diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac9a2e75..ff261bad 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} USER vscode -RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash +RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH -RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc +RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbeb30b1..c17fdc16 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,6 +24,9 @@ } } } + }, + "features": { + "ghcr.io/devcontainers/features/node:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40293964..fefaf86b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,28 +1,32 @@ name: CI on: push: - branches: - - main + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' pull_request: - branches: - - main - - next + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: + timeout-minutes: 10 name: lint - runs-on: ubuntu-latest - - + runs-on: ${{ github.repository == 'stainless-sdks/mixedbread-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: '0.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Install dependencies @@ -30,19 +34,61 @@ jobs: - name: Run lints run: ./scripts/lint + + build: + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + timeout-minutes: 10 + name: build + permissions: + contents: read + id-token: write + runs-on: ${{ github.repository == 'stainless-sdks/mixedbread-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + steps: + - uses: actions/checkout@v6 + + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Run build + run: rye build + + - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/mixedbread-python' + id: github-oidc + uses: actions/github-script@v8 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: github.repository == 'stainless-sdks/mixedbread-python' + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + test: + timeout-minutes: 10 name: test - runs-on: ubuntu-latest - + runs-on: ${{ github.repository == 'stainless-sdks/mixedbread-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: '0.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Bootstrap @@ -50,4 +96,3 @@ jobs: - name: Run tests run: ./scripts/test - diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 00000000..4bed0df6 --- /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/mixedbread-ai/mixedbread-python/actions/workflows/publish-pypi.yml +name: Publish PyPI +on: + workflow_dispatch: + + release: + types: [published] + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Publish to PyPI + run: | + bash ./bin/publish-pypi + env: + PYPI_TOKEN: ${{ secrets.MIXEDBREAD_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..5f4defaf --- /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 == 'mixedbread-ai/mixedbread-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v6 + + - name: Check release environment + run: | + bash ./bin/check-release-environment + env: + PYPI_TOKEN: ${{ secrets.MIXEDBREAD_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index 87797408..95ceb189 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .prism.log -.vscode _dev __pycache__ diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..fea34540 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.0" +} \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 17a58af2..8e94f914 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,4 @@ -configured_endpoints: 30 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/mixedbread%2Fmixedbread-a9b8728ee918c023a0e1458cfab7f26721d4ac851c533c6ce2f53411bc1576d1.yml +configured_endpoints: 56 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/mixedbread%2Fmixedbread-83afe8d98b70a903eb9aa13b512b18aa3df9e5ec4c784bded17ac78d0a163c3c.yml +openapi_spec_hash: 90b33b757e12f21c94705b6243054b5f +config_hash: c32ffa6858a02d7f23f6f3dda0b461ed diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5b010307 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.importFormat": "relative", +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ba0d641..ed6fa9a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix @@ -63,7 +62,7 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ pip install git+ssh://git@github.com/stainless-sdks/mixedbread-python.git +$ pip install git+ssh://git@github.com/mixedbread-ai/mixedbread-python.git ``` Alternatively, you can build from source and install the wheel file: @@ -89,8 +88,7 @@ $ pip install ./path-to-wheel-file.whl 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 +$ ./scripts/mock ``` ```sh @@ -121,7 +119,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/mixedbread-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/mixedbread-ai/mixedbread-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. ### Publish manually diff --git a/LICENSE b/LICENSE index 16abae3f..4c96cdcc 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 Mixedbread + Copyright 2026 Mixedbread 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 a22d9f2a..33fcbd13 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,46 @@ -# Mixedbread Python API library +# Mixedbread API Python SDK API library -[![PyPI version](https://img.shields.io/pypi/v/mixedbread.svg)](https://pypi.org/project/mixedbread/) + +[![PyPI version](https://img.shields.io/pypi/v/mixedbread.svg?label=pypi%20(stable))](https://pypi.org/project/mixedbread/) -The Mixedbread Python library provides convenient access to the Mixedbread REST API from any Python 3.8+ +The Mixedbread API Python SDK library provides convenient access to the Mixedbread 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). -It is generated with [Stainless](https://www.stainlessapi.com/). +It is generated with [Stainless](https://www.stainless.com/). ## Documentation -The REST API documentation can be found on [docs.mixedbread.com](https://docs.mixedbread.com). The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [mixedbread.com](https://mixedbread.com/docs). 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/mixedbread-python.git +# install from PyPI +pip install mixedbread ``` -> [!NOTE] -> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre mixedbread` - ## Usage The full API of this library can be found in [api.md](api.md). ```python +import os from mixedbread import Mixedbread client = Mixedbread( - # defaults to "production". - environment="environment_1", + api_key=os.environ.get("MXBAI_API_KEY"), # This is the default and can be omitted + # or 'production' | 'local'; defaults to "production". + environment="development", ) -file_object = client.files.create( - file=b"raw file contents", -) -print(file_object.id) +store = client.stores.create() +print(store.id) ``` While you can provide an `api_key` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `API_KEY="My API Key"` to your `.env` file +to add `MXBAI_API_KEY="My API Key"` to your `.env` file so that your API Key is not stored in source control. ## Async usage @@ -50,20 +48,20 @@ so that your API Key is not stored in source control. Simply import `AsyncMixedbread` instead of `Mixedbread` and use `await` with each API call: ```python +import os import asyncio from mixedbread import AsyncMixedbread client = AsyncMixedbread( - # defaults to "production". - environment="environment_1", + api_key=os.environ.get("MXBAI_API_KEY"), # This is the default and can be omitted + # or 'production' | 'local'; defaults to "production". + environment="development", ) async def main() -> None: - file_object = await client.files.create( - file=b"raw file contents", - ) - print(file_object.id) + store = await client.stores.create() + print(store.id) asyncio.run(main()) @@ -71,6 +69,38 @@ 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 mixedbread[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from mixedbread import DefaultAioHttpClient +from mixedbread import AsyncMixedbread + + +async def main() -> None: + async with AsyncMixedbread( + api_key=os.environ.get("MXBAI_API_KEY"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + store = await client.stores.create() + print(store.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: @@ -80,6 +110,101 @@ 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 Mixedbread 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 mixedbread import Mixedbread + +client = Mixedbread() + +all_stores = [] +# Automatically fetches more pages as needed. +for store in client.stores.list(): + # Do something with store here + all_stores.append(store) +print(all_stores) +``` + +Or, asynchronously: + +```python +import asyncio +from mixedbread import AsyncMixedbread + +client = AsyncMixedbread() + + +async def main() -> None: + all_stores = [] + # Iterate through items across all pages, issuing requests as needed. + async for store in client.stores.list(): + all_stores.append(store) + print(all_stores) + + +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.stores.list() +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.data)}") + +# Remove `await` for non-async usage. +``` + +Or just work directly with the returned data: + +```python +first_page = await client.stores.list() + +print(f"next page cursor: {first_page.pagination.last_cursor}") # => "next page cursor: ..." +for store in first_page.data: + print(store.id) + +# Remove `await` for non-async usage. +``` + +## Nested params + +Nested parameters are dictionaries, typed using `TypedDict`, for example: + +```python +from mixedbread import Mixedbread + +client = Mixedbread() + +store = client.stores.create( + expires_after={}, +) +print(store.expires_after) +``` + +## File uploads + +Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. + +```python +from pathlib import Path +from mixedbread import Mixedbread + +client = Mixedbread() + +client.files.create( + file=Path("/path/to/file"), +) +``` + +The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. + ## 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 `mixedbread.APIConnectionError` is raised. @@ -96,9 +221,7 @@ from mixedbread import Mixedbread client = Mixedbread() try: - client.files.create( - file=b"raw file contents", - ) + client.stores.create() except mixedbread.APIConnectionError as e: print("The server could not be reached") print(e.__cause__) # an underlying Exception, likely raised within httpx. @@ -141,15 +264,13 @@ client = Mixedbread( ) # Or, configure per-request: -client.with_options(max_retries=5).files.create( - file=b"raw file contents", -) +client.with_options(max_retries=5).stores.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 mixedbread import Mixedbread @@ -166,9 +287,7 @@ client = Mixedbread( ) # Override per-request: -client.with_options(timeout=5.0).files.create( - file=b"raw file contents", -) +client.with_options(timeout=5.0).stores.create() ``` On timeout, an `APITimeoutError` is thrown. @@ -209,18 +328,16 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from mixedbread import Mixedbread client = Mixedbread() -response = client.files.with_raw_response.create( - file=b'raw file contents', -) +response = client.stores.with_raw_response.create() print(response.headers.get('X-My-Header')) -file = response.parse() # get the object that `files.create()` would have returned -print(file.id) +store = response.parse() # get the object that `stores.create()` would have returned +print(store.id) ``` -These methods return an [`APIResponse`](https://github.com/stainless-sdks/mixedbread-python/tree/main/src/mixedbread/_response.py) object. +These methods return an [`APIResponse`](https://github.com/mixedbread-ai/mixedbread-python/tree/main/src/mixedbread/_response.py) object. -The async client returns an [`AsyncAPIResponse`](https://github.com/stainless-sdks/mixedbread-python/tree/main/src/mixedbread/_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/mixedbread-ai/mixedbread-python/tree/main/src/mixedbread/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. #### `.with_streaming_response` @@ -229,9 +346,7 @@ The above interface eagerly reads the full response body when you make the reque 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.files.with_streaming_response.create( - file=b"raw file contents", -) as response: +with client.stores.with_streaming_response.create() as response: print(response.headers.get("X-My-Header")) for line in response.iter_lines(): @@ -326,7 +441,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/mixedbread-python/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/mixedbread-ai/mixedbread-python/issues) with questions, bugs, or suggestions. ### Determining the installed version @@ -341,7 +456,7 @@ print(mixedbread.__version__) ## Requirements -Python 3.8 or higher. +Python 3.9 or higher. ## Contributing diff --git a/SECURITY.md b/SECURITY.md index 61396288..9485d544 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Reporting Security Issues -This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. -To report a security issue, please contact the Stainless team at security@stainlessapi.com. +To report a security issue, please contact the Stainless team at security@stainless.com. ## Responsible Disclosure @@ -16,11 +16,13 @@ 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 Mixedbread please follow the respective company's security reporting guidelines. +or products provided by Mixedbread, please follow the respective company's security reporting guidelines. ### Mixedbread Terms and Policies -Please contact dev-feedback@mixedbread.com for any questions or concerns regarding security of our services. +Our Security Policy can be found at [Security Policy URL](https://mixedbread.com/pages/privacy). + +Please contact support@mixedbread.com for any questions or concerns regarding the security of our services. --- diff --git a/api.md b/api.md index ab0ed54c..53e03831 100644 --- a/api.md +++ b/api.md @@ -1,108 +1,149 @@ -# ServiceInfo +# Shared Types + +```python +from mixedbread.types import SearchFilter, SearchFilterCondition, Usage +``` + +# Mixedbread Types: ```python -from mixedbread.types import InfoResponse +from mixedbread.types import ( + Embedding, + EmbeddingCreateResponse, + MultiEncodingEmbedding, + InfoResponse, + RerankResponse, +) ``` Methods: -- client.service_info.retrieve() -> InfoResponse +- client.embed(\*\*params) -> EmbeddingCreateResponse +- client.info() -> InfoResponse +- client.rerank(\*\*params) -> RerankResponse -# Files +# Stores Types: ```python -from mixedbread.types import FileDeleted, FileObject, FileListResponse +from mixedbread.types import ( + ExpiresAfter, + ScoredAudioURLInputChunk, + ScoredImageURLInputChunk, + ScoredTextInputChunk, + ScoredVideoURLInputChunk, + Store, + StoreChunkSearchOptions, + StoreDeleteResponse, + StoreMetadataFacetsResponse, + StoreQuestionAnsweringResponse, + StoreSearchResponse, +) ``` Methods: -- client.files.create(\*\*params) -> FileObject -- client.files.retrieve(file_id) -> FileObject -- client.files.update(file_id, \*\*params) -> FileObject -- client.files.list(\*\*params) -> FileListResponse -- client.files.delete(file_id) -> FileDeleted - -## Content - -Methods: - -- client.files.content.retrieve(file_id) -> BinaryAPIResponse +- client.stores.create(\*\*params) -> Store +- client.stores.retrieve(store_identifier) -> Store +- client.stores.update(store_identifier, \*\*params) -> Store +- client.stores.list(\*\*params) -> SyncCursor[Store] +- client.stores.delete(store_identifier) -> StoreDeleteResponse +- client.stores.metadata_facets(\*\*params) -> StoreMetadataFacetsResponse +- client.stores.question_answering(\*\*params) -> StoreQuestionAnsweringResponse +- client.stores.search(\*\*params) -> StoreSearchResponse -# Completions +## Files Types: ```python -from mixedbread.types import CompletionCreateResponse +from mixedbread.types.stores import ( + ScoredStoreFile, + StoreFileStatus, + StoreFile, + FileListResponse, + FileDeleteResponse, + FileSearchResponse, +) ``` Methods: -- client.completions.create() -> object +- client.stores.files.create(store_identifier, \*\*params) -> StoreFile +- client.stores.files.retrieve(file_identifier, \*, store_identifier, \*\*params) -> StoreFile +- client.stores.files.update(file_identifier, \*, store_identifier, \*\*params) -> StoreFile +- client.stores.files.list(store_identifier, \*\*params) -> FileListResponse +- client.stores.files.delete(file_identifier, \*, store_identifier) -> FileDeleteResponse +- client.stores.files.search(\*\*params) -> FileSearchResponse -# VectorStores +# Parsing + +## Jobs Types: ```python -from mixedbread.types import ( - VectorStore, - VectorStoreDeleted, - VectorStoreListResponse, - VectorStoreQuestionAnsweringResponse, - VectorStoreSearchResponse, +from mixedbread.types.parsing import ( + ChunkingStrategy, + ElementType, + ParsingJobStatus, + ParsingJob, + ReturnFormat, + JobListResponse, + JobDeleteResponse, ) ``` Methods: -- client.vector_stores.create(\*\*params) -> VectorStore -- client.vector_stores.retrieve(vector_store_id) -> VectorStore -- client.vector_stores.update(vector_store_id, \*\*params) -> VectorStore -- client.vector_stores.list(\*\*params) -> VectorStoreListResponse -- client.vector_stores.delete(vector_store_id) -> VectorStoreDeleted -- client.vector_stores.question_answering(\*\*params) -> object -- client.vector_stores.search(\*\*params) -> VectorStoreSearchResponse +- client.parsing.jobs.create(\*\*params) -> ParsingJob +- client.parsing.jobs.retrieve(job_id) -> ParsingJob +- client.parsing.jobs.list(\*\*params) -> SyncCursor[JobListResponse] +- client.parsing.jobs.delete(job_id) -> JobDeleteResponse +- client.parsing.jobs.cancel(job_id) -> ParsingJob -## Files +# Files Types: ```python -from mixedbread.types.vector_stores import ( - VectorStoreFile, - VectorStoreFileDeleted, - FileListResponse, - FileSearchResponse, -) +from mixedbread.types import FileObject, PaginationWithTotal, FileDeleteResponse ``` Methods: -- client.vector_stores.files.create(vector_store_id, \*\*params) -> VectorStoreFile -- client.vector_stores.files.retrieve(file_id, \*, vector_store_id) -> VectorStoreFile -- client.vector_stores.files.list(vector_store_id, \*\*params) -> FileListResponse -- client.vector_stores.files.delete(file_id, \*, vector_store_id) -> VectorStoreFileDeleted -- client.vector_stores.files.search(\*\*params) -> FileSearchResponse - -# Parsing +- client.files.create(\*\*params) -> FileObject +- client.files.retrieve(file_id) -> FileObject +- client.files.update(file_id, \*\*params) -> FileObject +- client.files.list(\*\*params) -> SyncCursor[FileObject] +- client.files.delete(file_id) -> FileDeleteResponse +- client.files.content(file_id) -> BinaryAPIResponse -## Jobs +## Uploads Types: ```python -from mixedbread.types.parsing import ParsingJob +from mixedbread.types.files import ( + MultipartUploadPart, + MultipartUploadPartURL, + UploadCreateResponse, + UploadRetrieveResponse, + UploadListResponse, + UploadAbortResponse, +) ``` Methods: -- client.parsing.jobs.create(\*\*params) -> ParsingJob -- client.parsing.jobs.retrieve(job_id) -> ParsingJob +- client.files.uploads.create(\*\*params) -> UploadCreateResponse +- client.files.uploads.retrieve(upload_id) -> UploadRetrieveResponse +- client.files.uploads.list() -> UploadListResponse +- client.files.uploads.abort(upload_id) -> UploadAbortResponse +- client.files.uploads.complete(upload_id, \*\*params) -> FileObject # Extractions @@ -138,7 +179,7 @@ Methods: Types: ```python -from mixedbread.types.extractions import ExtractionResult +from mixedbread.types.extractions import ExtractionResult, ImageURLInput, TextInput ``` Methods: @@ -150,21 +191,72 @@ Methods: Types: ```python -from mixedbread.types import EmbeddingCreateResponse +from mixedbread.types import EncodingFormat ``` Methods: - client.embeddings.create(\*\*params) -> EmbeddingCreateResponse -# Reranking +# DataSources + +Types: + +```python +from mixedbread.types import ( + DataSource, + DataSourceOauth2Params, + DataSourceType, + LinearDataSource, + NotionDataSource, + Oauth2Params, + DataSourceDeleteResponse, +) +``` + +Methods: + +- client.data_sources.create(\*\*params) -> DataSource +- client.data_sources.retrieve(data_source_id) -> DataSource +- client.data_sources.update(data_source_id, \*\*params) -> DataSource +- client.data_sources.list(\*\*params) -> SyncCursor[DataSource] +- client.data_sources.delete(data_source_id) -> DataSourceDeleteResponse + +## Connectors + +Types: + +```python +from mixedbread.types.data_sources import DataSourceConnector, ConnectorDeleteResponse +``` + +Methods: + +- client.data_sources.connectors.create(data_source_id, \*\*params) -> DataSourceConnector +- client.data_sources.connectors.retrieve(connector_id, \*, data_source_id) -> DataSourceConnector +- client.data_sources.connectors.update(connector_id, \*, data_source_id, \*\*params) -> DataSourceConnector +- client.data_sources.connectors.list(data_source_id, \*\*params) -> SyncCursor[DataSourceConnector] +- client.data_sources.connectors.delete(connector_id, \*, data_source_id) -> ConnectorDeleteResponse + +# APIKeys Types: ```python -from mixedbread.types import RerankingCreateResponse +from mixedbread.types import APIKey, APIKeyCreated, APIKeyDeleteResponse ``` Methods: -- client.reranking.create(\*\*params) -> RerankingCreateResponse +- client.api_keys.create(\*\*params) -> APIKeyCreated +- client.api_keys.retrieve(api_key_id) -> APIKey +- client.api_keys.list(\*\*params) -> SyncLimitOffset[APIKey] +- client.api_keys.delete(api_key_id) -> APIKeyDeleteResponse +- client.api_keys.reroll(api_key_id) -> APIKeyCreated +- client.api_keys.revoke(api_key_id) -> APIKey + +# Chat + +Methods: + +- client.chat.create_completion() -> object diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 00000000..b845b0f4 --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +errors=() + +if [ -z "${PYPI_TOKEN}" ]; then + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") +fi + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" diff --git a/bin/publish-pypi b/bin/publish-pypi index 05bfccbb..826054e9 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -3,7 +3,4 @@ set -eux mkdir -p dist rye build --clean -# Patching importlib-metadata version until upstream library version is updated -# https://github.com/pypa/twine/issues/977#issuecomment-2189800841 -"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1' rye publish --yes --token=$PYPI_TOKEN diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 4f4fe9c6..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/mixedbread/_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/pyproject.toml b/pyproject.toml index 9b12293b..e563f7e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,29 +1,32 @@ [project] name = "mixedbread" -version = "0.1.0-alpha.11" -description = "The official Python library for the mixedbread API" +version = "1.0.0" +description = "The official Python library for the Mixedbread API" dynamic = ["readme"] license = "Apache-2.0" authors = [ -{ name = "Mixedbread", email = "dev-feedback@mixedbread.com" }, +{ name = "Mixedbread", email = "support@mixedbread.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", + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.10, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", ] -requires-python = ">= 3.8" + +requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", @@ -34,17 +37,18 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/stainless-sdks/mixedbread-python" -Repository = "https://github.com/stainless-sdks/mixedbread-python" - +Homepage = "https://github.com/mixedbread-ai/mixedbread-python" +Repository = "https://github.com/mixedbread-ai/mixedbread-python" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] [tool.rye] managed = true # version pins are in requirements-dev.lock dev-dependencies = [ - "pyright>=1.1.359", - "mypy", + "pyright==1.1.399", + "mypy==1.17", "respx", "pytest", "pytest-asyncio", @@ -54,7 +58,7 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -65,7 +69,7 @@ format = { chain = [ # run formatting again to fix any inconsistencies when imports are stripped "format:ruff", ]} -"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" +"format:docs" = "bash -c 'python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md)'" "format:ruff" = "ruff format" "lint" = { chain = [ @@ -87,7 +91,7 @@ typecheck = { chain = [ "typecheck:mypy" = "mypy ." [build-system] -requires = ["hatchling", "hatch-fancy-pypi-readme"] +requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"] build-backend = "hatchling.build" [tool.hatch.build] @@ -122,13 +126,14 @@ 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/mixedbread-python/tree/main/\g<2>)' +replacement = '[\1](https://github.com/mixedbread-ai/mixedbread-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" filterwarnings = [ "error" ] @@ -138,24 +143,77 @@ filterwarnings = [ # there are a couple of flags that are still disabled by # default in strict mode as they are experimental and niche. typeCheckingMode = "strict" -pythonVersion = "3.8" +pythonVersion = "3.9" exclude = [ "_dev", ".venv", ".nox", + ".git", ] reportImplicitOverride = true +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/mixedbread/_files.py', '_dev/.*.py', 'tests/.*'] + +strict_equality = true +implicit_reexport = true +check_untyped_defs = true +no_implicit_optional = true + +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true + +# Turn these options off as it could cause conflicts +# with the Pyright options. +warn_unused_ignores = false +warn_redundant_casts = false + +disallow_any_generics = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +cache_fine_grained = true + +# By default, mypy reports an error if you assign a value to the result +# of a function call that doesn't return anything. We do this in our test +# cases: +# ``` +# result = ... +# assert result is None +# ``` +# Changing this codegen to make mypy happy would increase complexity +# and would not be worth it. +disable_error_code = "func-returns-value,overload-cannot-match" + +# https://github.com/python/mypy/issues/12162 +[[tool.mypy.overrides]] +module = "black.files.*" +ignore_errors = true +ignore_missing_imports = true + [tool.ruff] line-length = 120 output-format = "grouped" -target-version = "py37" +target-version = "py38" [tool.ruff.format] docstring-code-format = true @@ -168,6 +226,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -176,7 +236,7 @@ select = [ "T201", "T203", # misuse of typing.TYPE_CHECKING - "TCH004", + "TC004", # import rules "TID251", ] @@ -190,6 +250,8 @@ unfixable = [ "T203", ] +extend-safe-fixes = ["FA102"] + [tool.ruff.lint.flake8-tidy-imports.banned-api] "functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..0e2bc44e --- /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/mixedbread/_version.py" + ] +} \ No newline at end of file diff --git a/requirements-dev.lock b/requirements-dev.lock index f054d9bd..35a7b124 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,97 +7,143 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. -annotated-types==0.6.0 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.3 + # via httpx-aiohttp + # via mixedbread +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.1 # via httpx # via mixedbread -argcomplete==3.1.2 +argcomplete==3.6.3 + # via nox +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp # via nox -certifi==2023.7.22 +backports-asyncio-runner==1.2.0 + # via pytest-asyncio +certifi==2026.1.4 # via httpcore # via httpx -colorlog==6.7.0 +colorlog==6.10.1 + # via nox +dependency-groups==1.3.1 # via nox -dirty-equals==0.6.0 -distlib==0.3.7 +dirty-equals==0.11 +distlib==0.4.0 # via virtualenv -distro==1.8.0 +distro==1.9.0 # via mixedbread -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio # via pytest -filelock==3.12.4 +execnet==2.1.2 + # via pytest-xdist +filelock==3.19.1 # via virtualenv -h11==0.14.0 +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 + # via httpx-aiohttp # via mixedbread # via respx -idna==3.4 +httpx-aiohttp==0.1.12 + # via mixedbread +humanize==4.13.0 + # via nox +idna==3.11 # via anyio # via httpx -importlib-metadata==7.0.0 -iniconfig==2.0.0 + # via yarl +importlib-metadata==8.7.1 +iniconfig==2.1.0 # via pytest markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -mypy==1.13.0 -mypy-extensions==1.0.0 +multidict==6.7.0 + # via aiohttp + # via yarl +mypy==1.17.0 +mypy-extensions==1.1.0 # via mypy -nest-asyncio==1.6.0 -nodeenv==1.8.0 +nodeenv==1.10.0 # via pyright -nox==2023.4.22 -packaging==23.2 +nox==2025.11.12 +packaging==25.0 + # via dependency-groups # via nox # via pytest -platformdirs==3.11.0 +pathspec==1.0.3 + # via mypy +platformdirs==4.4.0 # via virtualenv -pluggy==1.5.0 +pluggy==1.6.0 # via pytest -pydantic==2.10.3 +propcache==0.4.1 + # via aiohttp + # via yarl +pydantic==2.12.5 # via mixedbread -pydantic-core==2.27.1 +pydantic-core==2.41.5 # via pydantic -pygments==2.18.0 +pygments==2.19.2 + # via pytest # via rich -pyright==1.1.390 -pytest==8.3.3 +pyright==1.1.399 +pytest==8.4.2 # via pytest-asyncio -pytest-asyncio==0.24.0 -python-dateutil==2.8.2 + # via pytest-xdist +pytest-asyncio==1.2.0 +pytest-xdist==3.8.0 +python-dateutil==2.9.0.post0 # via time-machine -pytz==2023.3.post1 - # via dirty-equals respx==0.22.0 -rich==13.7.1 -ruff==0.6.9 -setuptools==68.2.2 - # via nodeenv -six==1.16.0 +rich==14.2.0 +ruff==0.14.13 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via mixedbread -time-machine==2.9.0 -tomli==2.0.2 +time-machine==2.19.0 +tomli==2.4.0 + # via dependency-groups # via mypy + # via nox # via pytest -typing-extensions==4.12.2 +typing-extensions==4.15.0 + # via aiosignal # via anyio + # via exceptiongroup # via mixedbread + # via multidict # via mypy # via pydantic # via pydantic-core # via pyright -virtualenv==20.24.5 + # via pytest-asyncio + # via typing-inspection + # via virtualenv +typing-inspection==0.4.2 + # via pydantic +virtualenv==20.36.1 # via nox -zipp==3.17.0 +yarl==1.22.0 + # via aiohttp +zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 69e71cb9..ce962fbc 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,38 +7,70 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. -annotated-types==0.6.0 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.3 + # via httpx-aiohttp + # via mixedbread +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.1 # via httpx # via mixedbread -certifi==2023.7.22 +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp +certifi==2026.1.4 # via httpcore # via httpx -distro==1.8.0 +distro==1.9.0 # via mixedbread -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio -h11==0.14.0 +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 + # via httpx-aiohttp + # via mixedbread +httpx-aiohttp==0.1.12 # via mixedbread -idna==3.4 +idna==3.11 # via anyio # via httpx -pydantic==2.10.3 + # via yarl +multidict==6.7.0 + # via aiohttp + # via yarl +propcache==0.4.1 + # via aiohttp + # via yarl +pydantic==2.12.5 # via mixedbread -pydantic-core==2.27.1 +pydantic-core==2.41.5 # via pydantic -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via mixedbread -typing-extensions==4.12.2 +typing-extensions==4.15.0 + # via aiosignal # via anyio + # via exceptiongroup # via mixedbread + # via multidict # via pydantic # via pydantic-core + # via typing-inspection +typing-inspection==0.4.2 + # via pydantic +yarl==1.22.0 + # via aiohttp diff --git a/scripts/bootstrap b/scripts/bootstrap index 8c5c60eb..b430fee3 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi diff --git a/scripts/lint b/scripts/lint index 75fa7747..51bbef96 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,9 +4,13 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running lints" -rye run lint +if [ "$1" = "--fix" ]; then + echo "==> Running lints with --fix" + rye run fix:ruff +else + echo "==> Running lints" + rye run lint +fi echo "==> Making sure it imports" rye run python -c 'import mixedbread' - 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 diff --git a/scripts/test b/scripts/test index 4fa5698b..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 @@ -52,6 +52,8 @@ else echo fi +export DEFER_PYDANTIC_BUILD=false + echo "==> Running tests" rye run pytest "$@" diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py index 37b3d94f..0cf2bd2f 100644 --- a/scripts/utils/ruffen-docs.py +++ b/scripts/utils/ruffen-docs.py @@ -47,7 +47,7 @@ def _md_match(match: Match[str]) -> str: with _collect_error(match): code = format_code_block(code) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" def _pycon_match(match: Match[str]) -> str: code = "" @@ -97,7 +97,7 @@ def finish_fragment() -> None: def _md_pycon_match(match: Match[str]) -> str: code = _pycon_match(match) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" src = MD_RE.sub(_md_match, src) src = MD_PYCON_RE.sub(_md_pycon_match, src) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..5af44df7 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -exuo pipefail + +FILENAME=$(basename dist/*.whl) + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: binary/octet-stream" \ + --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/mixedbread-python/$SHA/$FILENAME'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/src/mixedbread/__init__.py b/src/mixedbread/__init__.py index c6b40046..82ae8048 100644 --- a/src/mixedbread/__init__.py +++ b/src/mixedbread/__init__.py @@ -1,7 +1,9 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import typing as _t + from . import types -from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path from ._client import ( ENVIRONMENTS, @@ -35,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__ = [ @@ -47,7 +49,9 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "not_given", "Omit", + "omit", "MixedbreadError", "APIError", "APIStatusError", @@ -78,8 +82,12 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + _setup_logging() # Update the __module__ attribute for exported symbols so that diff --git a/src/mixedbread/_base_client.py b/src/mixedbread/_base_client.py index 0be06c72..dded30e9 100644 --- a/src/mixedbread/_base_client.py +++ b/src/mixedbread/_base_client.py @@ -36,14 +36,13 @@ import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -51,19 +50,19 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, + BinaryTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import model_copy, model_dump +from ._compat import PYDANTIC_V1, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -87,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps log: logging.Logger = logging.getLogger(__name__) @@ -102,7 +102,11 @@ _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) if TYPE_CHECKING: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG else: try: from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT @@ -119,6 +123,7 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( @@ -134,19 +139,30 @@ def __init__( params: Query, ) -> None: ... + @overload def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + json: Body, + ) -> None: ... + + def __init__( + self, + *, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, ) -> None: self.url = url + self.json = json self.params = params @override def __repr__(self) -> str: if self.url: return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" return f"{self.__class__.__name__}(params={self.params})" @@ -195,6 +211,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options.url = str(url) return options + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + raise ValueError("Unexpected PageInfo state") @@ -207,6 +236,9 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -292,6 +324,9 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -331,9 +366,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _base_url: URL max_retries: int timeout: Union[float, Timeout, None] - _limits: httpx.Limits - _proxies: ProxiesTypes | None - _transport: Transport | AsyncTransport | None _strict_response_validation: bool _idempotency_header: str | None _default_stream_cls: type[_DefaultStreamT] | None = None @@ -346,9 +378,6 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, - limits: httpx.Limits, - transport: Transport | AsyncTransport | None, - proxies: ProxiesTypes | None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -356,9 +385,6 @@ def __init__( self._base_url = self._enforce_trailing_slash(URL(base_url)) self.max_retries = max_retries self.timeout = timeout - self._limits = limits - self._proxies = proxies - self._transport = transport self._custom_headers = custom_headers or {} self._custom_query = custom_query or {} self._strict_response_validation = _strict_response_validation @@ -415,13 +441,20 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 headers = httpx.Headers(headers_dict) idempotency_header = self._idempotency_header - if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: - headers[idempotency_header] = options.idempotency_key or self._idempotency_key() + if idempotency_header and options.idempotency_key and idempotency_header not in headers: + headers[idempotency_header] = options.idempotency_key - # Don't set 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 @@ -448,8 +481,19 @@ def _build_request( retries_taken: int = 0, ) -> httpx.Request: if log.isEnabledFor(logging.DEBUG): - log.debug("Request options: %s", model_dump(options, exclude_unset=True)) - + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) kwargs: dict[str, Any] = {} json_data = options.json_data @@ -500,6 +544,26 @@ def _build_request( # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + if options.content is not None and json_data is not None: + raise TypeError("Passing both `content` and `json_data` is not supported") + if options.content is not None and files is not None: + raise TypeError("Passing both `content` and `files` is not supported") + if options.content is not None: + kwargs["content"] = options.content + elif isinstance(json_data, bytes): + kwargs["content"] = json_data + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, @@ -511,8 +575,6 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data, - files=files, **kwargs, ) @@ -556,7 +618,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques # we internally support defining a temporary header to override the # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` # see _response.py for implementation details - override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) if is_given(override_cast_to): options.headers = headers return cast(Type[ResponseT], override_cast_to) @@ -786,47 +848,12 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: Transport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, ) -> None: - 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. @@ -847,12 +874,9 @@ def __init__( super().__init__( version=version, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, base_url=base_url, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -862,9 +886,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: @@ -914,7 +935,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], @@ -925,7 +945,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, ) -> ResponseT: ... @@ -935,7 +954,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: Type[_StreamT] | None = None, @@ -945,121 +963,112 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) + cast_to = self._maybe_override_cast_to(cast_to, options) - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - retries_taken: int, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - cast_to = self._maybe_override_cast_to(cast_to, options) - options = self._prepare_options(options) + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - self._prepare_request(request) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) - log.debug("Sending HTTP Request: %s %s", request.method, request.url) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects - 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) + 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, @@ -1069,37 +1078,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, *, @@ -1112,7 +1104,14 @@ def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1218,6 +1217,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, @@ -1230,6 +1230,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], @@ -1243,6 +1244,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, @@ -1255,13 +1257,25 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) @@ -1271,9 +1285,24 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) return self.request(cast_to, opts) def put( @@ -1282,11 +1311,23 @@ def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1296,9 +1337,19 @@ def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return self.request(cast_to, opts) def get_api_list( @@ -1323,6 +1374,24 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + if TYPE_CHECKING: DefaultAsyncHttpxClient = httpx.AsyncClient """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK @@ -1331,8 +1400,12 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): @@ -1358,46 +1431,11 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: AsyncTransport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: - 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. @@ -1419,11 +1457,8 @@ def __init__( super().__init__( version=version, base_url=base_url, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -1433,9 +1468,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: @@ -1484,7 +1516,6 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, ) -> ResponseT: ... @overload @@ -1495,7 +1526,6 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, ) -> _AsyncStreamT: ... @overload @@ -1506,7 +1536,6 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: ... async def request( @@ -1516,116 +1545,114 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) - - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - retries_taken: int, ) -> ResponseT | _AsyncStreamT: if self._platform is None: # `get_platform` can make blocking IO calls so we # execute it earlier while we are in an async context self._platform = await asyncify(get_platform)() + cast_to = self._maybe_override_cast_to(cast_to, options) + # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - cast_to = self._maybe_override_cast_to(cast_to, options) - options = await self._prepare_options(options) + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - await self._prepare_request(request) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - try: - response = await self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + 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, - ) + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - 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, @@ -1635,35 +1662,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, *, @@ -1676,7 +1688,14 @@ async def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") @@ -1770,6 +1789,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[False] = False, @@ -1782,6 +1802,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[True], @@ -1795,6 +1816,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, @@ -1807,13 +1829,25 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, ) -> ResponseT | _AsyncStreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) @@ -1823,9 +1857,29 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, + ) return await self.request(cast_to, opts) async def put( @@ -1834,11 +1888,23 @@ async def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) @@ -1848,9 +1914,19 @@ async def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return await self.request(cast_to, opts) def get_api_list( @@ -1874,8 +1950,8 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - post_parser: PostParser | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} diff --git a/src/mixedbread/_client.py b/src/mixedbread/_client.py index 3c02ec0f..7391953a 100644 --- a/src/mixedbread/_client.py +++ b/src/mixedbread/_client.py @@ -3,39 +3,65 @@ from __future__ import annotations import os -from typing import Any, Dict, Union, Mapping, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Mapping, Iterable, Optional, cast from typing_extensions import Self, Literal, override import httpx from . import _exceptions from ._qs import Querystring +from .types import client_embed_params, client_rerank_params from ._types import ( - NOT_GIVEN, + Body, Omit, + Query, + Headers, Timeout, NotGiven, Transport, ProxiesTypes, RequestOptions, + SequenceNotStr, + omit, + not_given, ) from ._utils import ( is_given, + maybe_transform, get_async_library, + async_maybe_transform, ) +from ._compat import cached_property from ._version import __version__ -from .resources import reranking, embeddings, completions, service_info +from ._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, MixedbreadError from ._base_client import ( DEFAULT_MAX_RETRIES, SyncAPIClient, AsyncAPIClient, + make_request_options, ) -from .resources.files import files -from .resources.parsing import parsing -from .resources.extractions import extractions -from .resources.vector_stores import vector_stores +from .types.info_response import InfoResponse +from .types.encoding_format import EncodingFormat +from .types.rerank_response import RerankResponse +from .types.embedding_create_response import EmbeddingCreateResponse + +if TYPE_CHECKING: + from .resources import chat, files, stores, parsing, api_keys, embeddings, extractions, data_sources + from .resources.chat import ChatResource, AsyncChatResource + from .resources.api_keys import APIKeysResource, AsyncAPIKeysResource + from .resources.embeddings import EmbeddingsResource, AsyncEmbeddingsResource + from .resources.files.files import FilesResource, AsyncFilesResource + from .resources.stores.stores import StoresResource, AsyncStoresResource + from .resources.parsing.parsing import ParsingResource, AsyncParsingResource + from .resources.extractions.extractions import ExtractionsResource, AsyncExtractionsResource + from .resources.data_sources.data_sources import DataSourcesResource, AsyncDataSourcesResource __all__ = [ "ENVIRONMENTS", @@ -50,35 +76,25 @@ ] ENVIRONMENTS: Dict[str, str] = { - "production": "https://api.mixedbread.ai", - "environment_1": "http://127.0.0.1:8000", + "production": "https://api.mixedbread.com", + "development": "https://api.dev.mixedbread.com", + "local": "http://127.0.0.1:8000", } class Mixedbread(SyncAPIClient): - service_info: service_info.ServiceInfoResource - files: files.FilesResource - completions: completions.CompletionsResource - vector_stores: vector_stores.VectorStoresResource - parsing: parsing.ParsingResource - extractions: extractions.ExtractionsResource - embeddings: embeddings.EmbeddingsResource - reranking: reranking.RerankingResource - with_raw_response: MixedbreadWithRawResponse - with_streaming_response: MixedbreadWithStreamedResponse - # client options api_key: str - _environment: Literal["production", "environment_1"] | NotGiven + _environment: Literal["production", "development", "local"] | NotGiven def __init__( self, *, api_key: str | None = None, - environment: Literal["production", "environment_1"] | NotGiven = NOT_GIVEN, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + environment: Literal["production", "development", "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, @@ -96,15 +112,15 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous mixedbread client instance. + """Construct a new synchronous Mixedbread client instance. - This automatically infers the `api_key` argument from the `API_KEY` environment variable if it is not provided. + This automatically infers the `api_key` argument from the `MXBAI_API_KEY` environment variable if it is not provided. """ if api_key is None: - api_key = os.environ.get("API_KEY") + api_key = os.environ.get("MXBAI_API_KEY") if api_key is None: raise MixedbreadError( - "The api_key client option must be set either by passing api_key to the client or by setting the API_KEY environment variable" + "The api_key client option must be set either by passing api_key to the client or by setting the MXBAI_API_KEY environment variable" ) self.api_key = api_key @@ -145,21 +161,66 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.service_info = service_info.ServiceInfoResource(self) - self.files = files.FilesResource(self) - self.completions = completions.CompletionsResource(self) - self.vector_stores = vector_stores.VectorStoresResource(self) - self.parsing = parsing.ParsingResource(self) - self.extractions = extractions.ExtractionsResource(self) - self.embeddings = embeddings.EmbeddingsResource(self) - self.reranking = reranking.RerankingResource(self) - self.with_raw_response = MixedbreadWithRawResponse(self) - self.with_streaming_response = MixedbreadWithStreamedResponse(self) + @cached_property + def stores(self) -> StoresResource: + from .resources.stores import StoresResource + + return StoresResource(self) + + @cached_property + def parsing(self) -> ParsingResource: + from .resources.parsing import ParsingResource + + return ParsingResource(self) + + @cached_property + def files(self) -> FilesResource: + from .resources.files import FilesResource + + return FilesResource(self) + + @cached_property + def extractions(self) -> ExtractionsResource: + from .resources.extractions import ExtractionsResource + + return ExtractionsResource(self) + + @cached_property + def embeddings(self) -> EmbeddingsResource: + from .resources.embeddings import EmbeddingsResource + + return EmbeddingsResource(self) + + @cached_property + def data_sources(self) -> DataSourcesResource: + from .resources.data_sources import DataSourcesResource + + return DataSourcesResource(self) + + @cached_property + def api_keys(self) -> APIKeysResource: + from .resources.api_keys import APIKeysResource + + return APIKeysResource(self) + + @cached_property + def chat(self) -> ChatResource: + from .resources.chat import ChatResource + + return ChatResource(self) + + @cached_property + def with_raw_response(self) -> MixedbreadWithRawResponse: + return MixedbreadWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> MixedbreadWithStreamedResponse: + return MixedbreadWithStreamedResponse(self) @property @override def qs(self) -> Querystring: - return Querystring(array_format="comma") + return Querystring(array_format="repeat") @property @override @@ -180,11 +241,11 @@ def copy( self, *, api_key: str | None = None, - environment: Literal["production", "environment_1"] | None = None, + environment: Literal["production", "development", "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, @@ -229,6 +290,161 @@ def copy( # client.with_options(timeout=10).foo.create(...) with_options = copy + def embed( + self, + *, + model: str, + input: Union[str, SequenceNotStr[str]], + dimensions: Optional[int] | Omit = omit, + prompt: Optional[str] | Omit = omit, + normalized: bool | Omit = omit, + encoding_format: Union[EncodingFormat, List[EncodingFormat]] | 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, + ) -> EmbeddingCreateResponse: + """ + Create embeddings for text or images using the specified model, encoding format, + and normalization. + + Args: params: The parameters for creating embeddings. + + Returns: EmbeddingCreateResponse: The response containing the embeddings. + + Args: + model: The model to use for creating embeddings. + + input: The input to create embeddings for. + + dimensions: The number of dimensions to use for the embeddings. + + prompt: The prompt to use for the embedding creation. + + normalized: Whether to normalize the embeddings. + + encoding_format: The encoding format(s) of the embeddings. Can be a single format or a list of + formats. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/embeddings", + body=maybe_transform( + { + "model": model, + "input": input, + "dimensions": dimensions, + "prompt": prompt, + "normalized": normalized, + "encoding_format": encoding_format, + }, + client_embed_params.ClientEmbedParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EmbeddingCreateResponse, + ) + + def info( + 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, + ) -> InfoResponse: + """ + Returns service information, including name and version. + + Returns: InfoResponse: A response containing the service name and version. + """ + return self.get( + "/", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InfoResponse, + ) + + def rerank( + self, + *, + model: str | Omit = omit, + query: str, + input: SequenceNotStr[Union[str, Iterable[object], object]], + rank_fields: Optional[SequenceNotStr[str]] | Omit = omit, + top_k: int | Omit = omit, + return_input: bool | Omit = omit, + rewrite_query: 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, + ) -> RerankResponse: + """ + Rerank different kind of documents for a given query. + + Args: params: RerankParams: The parameters for reranking. + + Returns: RerankResponse: The reranked documents for the input query. + + Args: + model: The model to use for reranking documents. + + query: The query to rerank the documents. + + input: The input documents to rerank. + + rank_fields: The fields of the documents to rank. + + top_k: The number of documents to return. + + return_input: Whether to return the documents. + + rewrite_query: Wether or not to rewrite the query before passing it to the reranking model + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/reranking", + body=maybe_transform( + { + "model": model, + "query": query, + "input": input, + "rank_fields": rank_fields, + "top_k": top_k, + "return_input": return_input, + "rewrite_query": rewrite_query, + }, + client_rerank_params.ClientRerankParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RerankResponse, + ) + @override def _make_status_error( self, @@ -264,29 +480,18 @@ def _make_status_error( class AsyncMixedbread(AsyncAPIClient): - service_info: service_info.AsyncServiceInfoResource - files: files.AsyncFilesResource - completions: completions.AsyncCompletionsResource - vector_stores: vector_stores.AsyncVectorStoresResource - parsing: parsing.AsyncParsingResource - extractions: extractions.AsyncExtractionsResource - embeddings: embeddings.AsyncEmbeddingsResource - reranking: reranking.AsyncRerankingResource - with_raw_response: AsyncMixedbreadWithRawResponse - with_streaming_response: AsyncMixedbreadWithStreamedResponse - # client options api_key: str - _environment: Literal["production", "environment_1"] | NotGiven + _environment: Literal["production", "development", "local"] | NotGiven def __init__( self, *, api_key: str | None = None, - environment: Literal["production", "environment_1"] | NotGiven = NOT_GIVEN, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + environment: Literal["production", "development", "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, @@ -304,15 +509,15 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async mixedbread client instance. + """Construct a new async AsyncMixedbread client instance. - This automatically infers the `api_key` argument from the `API_KEY` environment variable if it is not provided. + This automatically infers the `api_key` argument from the `MXBAI_API_KEY` environment variable if it is not provided. """ if api_key is None: - api_key = os.environ.get("API_KEY") + api_key = os.environ.get("MXBAI_API_KEY") if api_key is None: raise MixedbreadError( - "The api_key client option must be set either by passing api_key to the client or by setting the API_KEY environment variable" + "The api_key client option must be set either by passing api_key to the client or by setting the MXBAI_API_KEY environment variable" ) self.api_key = api_key @@ -353,21 +558,66 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.service_info = service_info.AsyncServiceInfoResource(self) - self.files = files.AsyncFilesResource(self) - self.completions = completions.AsyncCompletionsResource(self) - self.vector_stores = vector_stores.AsyncVectorStoresResource(self) - self.parsing = parsing.AsyncParsingResource(self) - self.extractions = extractions.AsyncExtractionsResource(self) - self.embeddings = embeddings.AsyncEmbeddingsResource(self) - self.reranking = reranking.AsyncRerankingResource(self) - self.with_raw_response = AsyncMixedbreadWithRawResponse(self) - self.with_streaming_response = AsyncMixedbreadWithStreamedResponse(self) + @cached_property + def stores(self) -> AsyncStoresResource: + from .resources.stores import AsyncStoresResource + + return AsyncStoresResource(self) + + @cached_property + def parsing(self) -> AsyncParsingResource: + from .resources.parsing import AsyncParsingResource + + return AsyncParsingResource(self) + + @cached_property + def files(self) -> AsyncFilesResource: + from .resources.files import AsyncFilesResource + + return AsyncFilesResource(self) + + @cached_property + def extractions(self) -> AsyncExtractionsResource: + from .resources.extractions import AsyncExtractionsResource + + return AsyncExtractionsResource(self) + + @cached_property + def embeddings(self) -> AsyncEmbeddingsResource: + from .resources.embeddings import AsyncEmbeddingsResource + + return AsyncEmbeddingsResource(self) + + @cached_property + def data_sources(self) -> AsyncDataSourcesResource: + from .resources.data_sources import AsyncDataSourcesResource + + return AsyncDataSourcesResource(self) + + @cached_property + def api_keys(self) -> AsyncAPIKeysResource: + from .resources.api_keys import AsyncAPIKeysResource + + return AsyncAPIKeysResource(self) + + @cached_property + def chat(self) -> AsyncChatResource: + from .resources.chat import AsyncChatResource + + return AsyncChatResource(self) + + @cached_property + def with_raw_response(self) -> AsyncMixedbreadWithRawResponse: + return AsyncMixedbreadWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncMixedbreadWithStreamedResponse: + return AsyncMixedbreadWithStreamedResponse(self) @property @override def qs(self) -> Querystring: - return Querystring(array_format="comma") + return Querystring(array_format="repeat") @property @override @@ -388,11 +638,11 @@ def copy( self, *, api_key: str | None = None, - environment: Literal["production", "environment_1"] | None = None, + environment: Literal["production", "development", "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, @@ -437,6 +687,161 @@ def copy( # client.with_options(timeout=10).foo.create(...) with_options = copy + async def embed( + self, + *, + model: str, + input: Union[str, SequenceNotStr[str]], + dimensions: Optional[int] | Omit = omit, + prompt: Optional[str] | Omit = omit, + normalized: bool | Omit = omit, + encoding_format: Union[EncodingFormat, List[EncodingFormat]] | 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, + ) -> EmbeddingCreateResponse: + """ + Create embeddings for text or images using the specified model, encoding format, + and normalization. + + Args: params: The parameters for creating embeddings. + + Returns: EmbeddingCreateResponse: The response containing the embeddings. + + Args: + model: The model to use for creating embeddings. + + input: The input to create embeddings for. + + dimensions: The number of dimensions to use for the embeddings. + + prompt: The prompt to use for the embedding creation. + + normalized: Whether to normalize the embeddings. + + encoding_format: The encoding format(s) of the embeddings. Can be a single format or a list of + formats. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/embeddings", + body=await async_maybe_transform( + { + "model": model, + "input": input, + "dimensions": dimensions, + "prompt": prompt, + "normalized": normalized, + "encoding_format": encoding_format, + }, + client_embed_params.ClientEmbedParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EmbeddingCreateResponse, + ) + + async def info( + 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, + ) -> InfoResponse: + """ + Returns service information, including name and version. + + Returns: InfoResponse: A response containing the service name and version. + """ + return await self.get( + "/", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InfoResponse, + ) + + async def rerank( + self, + *, + model: str | Omit = omit, + query: str, + input: SequenceNotStr[Union[str, Iterable[object], object]], + rank_fields: Optional[SequenceNotStr[str]] | Omit = omit, + top_k: int | Omit = omit, + return_input: bool | Omit = omit, + rewrite_query: 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, + ) -> RerankResponse: + """ + Rerank different kind of documents for a given query. + + Args: params: RerankParams: The parameters for reranking. + + Returns: RerankResponse: The reranked documents for the input query. + + Args: + model: The model to use for reranking documents. + + query: The query to rerank the documents. + + input: The input documents to rerank. + + rank_fields: The fields of the documents to rank. + + top_k: The number of documents to return. + + return_input: Whether to return the documents. + + rewrite_query: Wether or not to rewrite the query before passing it to the reranking model + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/reranking", + body=await async_maybe_transform( + { + "model": model, + "query": query, + "input": input, + "rank_fields": rank_fields, + "top_k": top_k, + "return_input": return_input, + "rewrite_query": rewrite_query, + }, + client_rerank_params.ClientRerankParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RerankResponse, + ) + @override def _make_status_error( self, @@ -472,51 +877,263 @@ def _make_status_error( class MixedbreadWithRawResponse: + _client: Mixedbread + def __init__(self, client: Mixedbread) -> None: - self.service_info = service_info.ServiceInfoResourceWithRawResponse(client.service_info) - self.files = files.FilesResourceWithRawResponse(client.files) - self.completions = completions.CompletionsResourceWithRawResponse(client.completions) - self.vector_stores = vector_stores.VectorStoresResourceWithRawResponse(client.vector_stores) - self.parsing = parsing.ParsingResourceWithRawResponse(client.parsing) - self.extractions = extractions.ExtractionsResourceWithRawResponse(client.extractions) - self.embeddings = embeddings.EmbeddingsResourceWithRawResponse(client.embeddings) - self.reranking = reranking.RerankingResourceWithRawResponse(client.reranking) + self._client = client + + self.embed = to_raw_response_wrapper( + client.embed, + ) + self.info = to_raw_response_wrapper( + client.info, + ) + self.rerank = to_raw_response_wrapper( + client.rerank, + ) + + @cached_property + def stores(self) -> stores.StoresResourceWithRawResponse: + from .resources.stores import StoresResourceWithRawResponse + + return StoresResourceWithRawResponse(self._client.stores) + + @cached_property + def parsing(self) -> parsing.ParsingResourceWithRawResponse: + from .resources.parsing import ParsingResourceWithRawResponse + + return ParsingResourceWithRawResponse(self._client.parsing) + + @cached_property + def files(self) -> files.FilesResourceWithRawResponse: + from .resources.files import FilesResourceWithRawResponse + + return FilesResourceWithRawResponse(self._client.files) + + @cached_property + def extractions(self) -> extractions.ExtractionsResourceWithRawResponse: + from .resources.extractions import ExtractionsResourceWithRawResponse + + return ExtractionsResourceWithRawResponse(self._client.extractions) + + @cached_property + def embeddings(self) -> embeddings.EmbeddingsResourceWithRawResponse: + from .resources.embeddings import EmbeddingsResourceWithRawResponse + + return EmbeddingsResourceWithRawResponse(self._client.embeddings) + + @cached_property + def data_sources(self) -> data_sources.DataSourcesResourceWithRawResponse: + from .resources.data_sources import DataSourcesResourceWithRawResponse + + return DataSourcesResourceWithRawResponse(self._client.data_sources) + + @cached_property + def api_keys(self) -> api_keys.APIKeysResourceWithRawResponse: + from .resources.api_keys import APIKeysResourceWithRawResponse + + return APIKeysResourceWithRawResponse(self._client.api_keys) + + @cached_property + def chat(self) -> chat.ChatResourceWithRawResponse: + from .resources.chat import ChatResourceWithRawResponse + + return ChatResourceWithRawResponse(self._client.chat) class AsyncMixedbreadWithRawResponse: + _client: AsyncMixedbread + def __init__(self, client: AsyncMixedbread) -> None: - self.service_info = service_info.AsyncServiceInfoResourceWithRawResponse(client.service_info) - self.files = files.AsyncFilesResourceWithRawResponse(client.files) - self.completions = completions.AsyncCompletionsResourceWithRawResponse(client.completions) - self.vector_stores = vector_stores.AsyncVectorStoresResourceWithRawResponse(client.vector_stores) - self.parsing = parsing.AsyncParsingResourceWithRawResponse(client.parsing) - self.extractions = extractions.AsyncExtractionsResourceWithRawResponse(client.extractions) - self.embeddings = embeddings.AsyncEmbeddingsResourceWithRawResponse(client.embeddings) - self.reranking = reranking.AsyncRerankingResourceWithRawResponse(client.reranking) + self._client = client + + self.embed = async_to_raw_response_wrapper( + client.embed, + ) + self.info = async_to_raw_response_wrapper( + client.info, + ) + self.rerank = async_to_raw_response_wrapper( + client.rerank, + ) + + @cached_property + def stores(self) -> stores.AsyncStoresResourceWithRawResponse: + from .resources.stores import AsyncStoresResourceWithRawResponse + + return AsyncStoresResourceWithRawResponse(self._client.stores) + + @cached_property + def parsing(self) -> parsing.AsyncParsingResourceWithRawResponse: + from .resources.parsing import AsyncParsingResourceWithRawResponse + + return AsyncParsingResourceWithRawResponse(self._client.parsing) + + @cached_property + def files(self) -> files.AsyncFilesResourceWithRawResponse: + from .resources.files import AsyncFilesResourceWithRawResponse + + return AsyncFilesResourceWithRawResponse(self._client.files) + + @cached_property + def extractions(self) -> extractions.AsyncExtractionsResourceWithRawResponse: + from .resources.extractions import AsyncExtractionsResourceWithRawResponse + + return AsyncExtractionsResourceWithRawResponse(self._client.extractions) + + @cached_property + def embeddings(self) -> embeddings.AsyncEmbeddingsResourceWithRawResponse: + from .resources.embeddings import AsyncEmbeddingsResourceWithRawResponse + + return AsyncEmbeddingsResourceWithRawResponse(self._client.embeddings) + + @cached_property + def data_sources(self) -> data_sources.AsyncDataSourcesResourceWithRawResponse: + from .resources.data_sources import AsyncDataSourcesResourceWithRawResponse + + return AsyncDataSourcesResourceWithRawResponse(self._client.data_sources) + + @cached_property + def api_keys(self) -> api_keys.AsyncAPIKeysResourceWithRawResponse: + from .resources.api_keys import AsyncAPIKeysResourceWithRawResponse + + return AsyncAPIKeysResourceWithRawResponse(self._client.api_keys) + + @cached_property + def chat(self) -> chat.AsyncChatResourceWithRawResponse: + from .resources.chat import AsyncChatResourceWithRawResponse + + return AsyncChatResourceWithRawResponse(self._client.chat) class MixedbreadWithStreamedResponse: + _client: Mixedbread + def __init__(self, client: Mixedbread) -> None: - self.service_info = service_info.ServiceInfoResourceWithStreamingResponse(client.service_info) - self.files = files.FilesResourceWithStreamingResponse(client.files) - self.completions = completions.CompletionsResourceWithStreamingResponse(client.completions) - self.vector_stores = vector_stores.VectorStoresResourceWithStreamingResponse(client.vector_stores) - self.parsing = parsing.ParsingResourceWithStreamingResponse(client.parsing) - self.extractions = extractions.ExtractionsResourceWithStreamingResponse(client.extractions) - self.embeddings = embeddings.EmbeddingsResourceWithStreamingResponse(client.embeddings) - self.reranking = reranking.RerankingResourceWithStreamingResponse(client.reranking) + self._client = client + + self.embed = to_streamed_response_wrapper( + client.embed, + ) + self.info = to_streamed_response_wrapper( + client.info, + ) + self.rerank = to_streamed_response_wrapper( + client.rerank, + ) + + @cached_property + def stores(self) -> stores.StoresResourceWithStreamingResponse: + from .resources.stores import StoresResourceWithStreamingResponse + + return StoresResourceWithStreamingResponse(self._client.stores) + + @cached_property + def parsing(self) -> parsing.ParsingResourceWithStreamingResponse: + from .resources.parsing import ParsingResourceWithStreamingResponse + + return ParsingResourceWithStreamingResponse(self._client.parsing) + + @cached_property + def files(self) -> files.FilesResourceWithStreamingResponse: + from .resources.files import FilesResourceWithStreamingResponse + + return FilesResourceWithStreamingResponse(self._client.files) + + @cached_property + def extractions(self) -> extractions.ExtractionsResourceWithStreamingResponse: + from .resources.extractions import ExtractionsResourceWithStreamingResponse + + return ExtractionsResourceWithStreamingResponse(self._client.extractions) + + @cached_property + def embeddings(self) -> embeddings.EmbeddingsResourceWithStreamingResponse: + from .resources.embeddings import EmbeddingsResourceWithStreamingResponse + + return EmbeddingsResourceWithStreamingResponse(self._client.embeddings) + + @cached_property + def data_sources(self) -> data_sources.DataSourcesResourceWithStreamingResponse: + from .resources.data_sources import DataSourcesResourceWithStreamingResponse + + return DataSourcesResourceWithStreamingResponse(self._client.data_sources) + + @cached_property + def api_keys(self) -> api_keys.APIKeysResourceWithStreamingResponse: + from .resources.api_keys import APIKeysResourceWithStreamingResponse + + return APIKeysResourceWithStreamingResponse(self._client.api_keys) + + @cached_property + def chat(self) -> chat.ChatResourceWithStreamingResponse: + from .resources.chat import ChatResourceWithStreamingResponse + + return ChatResourceWithStreamingResponse(self._client.chat) class AsyncMixedbreadWithStreamedResponse: + _client: AsyncMixedbread + def __init__(self, client: AsyncMixedbread) -> None: - self.service_info = service_info.AsyncServiceInfoResourceWithStreamingResponse(client.service_info) - self.files = files.AsyncFilesResourceWithStreamingResponse(client.files) - self.completions = completions.AsyncCompletionsResourceWithStreamingResponse(client.completions) - self.vector_stores = vector_stores.AsyncVectorStoresResourceWithStreamingResponse(client.vector_stores) - self.parsing = parsing.AsyncParsingResourceWithStreamingResponse(client.parsing) - self.extractions = extractions.AsyncExtractionsResourceWithStreamingResponse(client.extractions) - self.embeddings = embeddings.AsyncEmbeddingsResourceWithStreamingResponse(client.embeddings) - self.reranking = reranking.AsyncRerankingResourceWithStreamingResponse(client.reranking) + self._client = client + + self.embed = async_to_streamed_response_wrapper( + client.embed, + ) + self.info = async_to_streamed_response_wrapper( + client.info, + ) + self.rerank = async_to_streamed_response_wrapper( + client.rerank, + ) + + @cached_property + def stores(self) -> stores.AsyncStoresResourceWithStreamingResponse: + from .resources.stores import AsyncStoresResourceWithStreamingResponse + + return AsyncStoresResourceWithStreamingResponse(self._client.stores) + + @cached_property + def parsing(self) -> parsing.AsyncParsingResourceWithStreamingResponse: + from .resources.parsing import AsyncParsingResourceWithStreamingResponse + + return AsyncParsingResourceWithStreamingResponse(self._client.parsing) + + @cached_property + def files(self) -> files.AsyncFilesResourceWithStreamingResponse: + from .resources.files import AsyncFilesResourceWithStreamingResponse + + return AsyncFilesResourceWithStreamingResponse(self._client.files) + + @cached_property + def extractions(self) -> extractions.AsyncExtractionsResourceWithStreamingResponse: + from .resources.extractions import AsyncExtractionsResourceWithStreamingResponse + + return AsyncExtractionsResourceWithStreamingResponse(self._client.extractions) + + @cached_property + def embeddings(self) -> embeddings.AsyncEmbeddingsResourceWithStreamingResponse: + from .resources.embeddings import AsyncEmbeddingsResourceWithStreamingResponse + + return AsyncEmbeddingsResourceWithStreamingResponse(self._client.embeddings) + + @cached_property + def data_sources(self) -> data_sources.AsyncDataSourcesResourceWithStreamingResponse: + from .resources.data_sources import AsyncDataSourcesResourceWithStreamingResponse + + return AsyncDataSourcesResourceWithStreamingResponse(self._client.data_sources) + + @cached_property + def api_keys(self) -> api_keys.AsyncAPIKeysResourceWithStreamingResponse: + from .resources.api_keys import AsyncAPIKeysResourceWithStreamingResponse + + return AsyncAPIKeysResourceWithStreamingResponse(self._client.api_keys) + + @cached_property + def chat(self) -> chat.AsyncChatResourceWithStreamingResponse: + from .resources.chat import AsyncChatResourceWithStreamingResponse + + return AsyncChatResourceWithStreamingResponse(self._client.chat) Client = Mixedbread diff --git a/src/mixedbread/_compat.py b/src/mixedbread/_compat.py index 92d9ee61..786ff42a 100644 --- a/src/mixedbread/_compat.py +++ b/src/mixedbread/_compat.py @@ -12,14 +12,13 @@ _T = TypeVar("_T") _ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) -# --------------- Pydantic v2 compatibility --------------- +# --------------- Pydantic v2, v3 compatibility --------------- # Pyright incorrectly reports some of our functions as overriding a method when they don't # pyright: reportIncompatibleMethodOverride=false -PYDANTIC_V2 = pydantic.VERSION.startswith("2.") +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") -# v1 re-exports if TYPE_CHECKING: def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 @@ -44,90 +43,92 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 ... else: - if PYDANTIC_V2: - from pydantic.v1.typing import ( + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( get_args as get_args, is_union as is_union, get_origin as get_origin, is_typeddict as is_typeddict, is_literal_type as is_literal_type, ) - from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime else: - from pydantic.typing import ( + from ._utils import ( get_args as get_args, is_union as is_union, get_origin as get_origin, + parse_date as parse_date, is_typeddict as is_typeddict, + parse_datetime as parse_datetime, is_literal_type as is_literal_type, ) - from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime # refactored config if TYPE_CHECKING: from pydantic import ConfigDict as ConfigDict else: - if PYDANTIC_V2: - from pydantic import ConfigDict - else: + if PYDANTIC_V1: # TODO: provide an error message here? ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict # renamed methods / properties def parse_obj(model: type[_ModelT], value: object) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(value) - else: + if PYDANTIC_V1: return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) def field_is_required(field: FieldInfo) -> bool: - if PYDANTIC_V2: - return field.is_required() - return field.required # type: ignore + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() def field_get_default(field: FieldInfo) -> Any: value = field.get_default() - if PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None + if PYDANTIC_V1: return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None return value def field_outer_type(field: FieldInfo) -> Any: - if PYDANTIC_V2: - return field.annotation - return field.outer_type_ # type: ignore + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation def get_model_config(model: type[pydantic.BaseModel]) -> Any: - if PYDANTIC_V2: - return model.model_config - return model.__config__ # type: ignore + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: - if PYDANTIC_V2: - return model.model_fields - return model.__fields__ # type: ignore + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: - if PYDANTIC_V2: - return model.model_copy(deep=deep) - return model.copy(deep=deep) # type: ignore + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: - if PYDANTIC_V2: - return model.model_dump_json(indent=indent) - return model.json(indent=indent) # type: ignore + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) def model_dump( @@ -138,30 +139,30 @@ def model_dump( exclude_defaults: bool = False, warnings: bool = True, mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: - if PYDANTIC_V2 or hasattr(model, "model_dump"): + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( mode=mode, exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 - warnings=warnings if PYDANTIC_V2 else True, + warnings=True if PYDANTIC_V1 else warnings, + by_alias=by_alias, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) ), ) def model_parse(model: type[_ModelT], data: Any) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(data) - return model.parse_obj(data) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) # generic models @@ -170,17 +171,16 @@ def model_parse(model: type[_ModelT], data: Any) -> _ModelT: class GenericModel(pydantic.BaseModel): ... else: - if PYDANTIC_V2: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: # there no longer needs to be a distinction in v2 but # we still have to create our own subclass to avoid # inconsistent MRO ordering errors class GenericModel(pydantic.BaseModel): ... - else: - import pydantic.generics - - class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... - # cached properties if TYPE_CHECKING: diff --git a/src/mixedbread/_constants.py b/src/mixedbread/_constants.py index a2ac3b6f..6ddf2c71 100644 --- a/src/mixedbread/_constants.py +++ b/src/mixedbread/_constants.py @@ -6,7 +6,7 @@ OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" # default timeout is 1 minute -DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0) +DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) DEFAULT_MAX_RETRIES = 2 DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) diff --git a/src/mixedbread/_files.py b/src/mixedbread/_files.py index 715cc207..729ea489 100644 --- a/src/mixedbread/_files.py +++ b/src/mixedbread/_files.py @@ -34,7 +34,7 @@ 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." + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/mixedbread-ai/mixedbread-python/tree/main#file-uploads" ) from None @@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], _read_file_content(file[1]), *file[2:]) + return (file[0], read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -def _read_file_content(file: FileContent) -> HttpxFileContent: +def read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return pathlib.Path(file).read_bytes() return file @@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], await _async_read_file_content(file[1]), *file[2:]) + return (file[0], await async_read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -async def _async_read_file_content(file: FileContent) -> HttpxFileContent: +async def async_read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return await anyio.Path(file).read_bytes() diff --git a/src/mixedbread/_models.py b/src/mixedbread/_models.py index 9a918aab..29070e05 100644 --- a/src/mixedbread/_models.py +++ b/src/mixedbread/_models.py @@ -2,9 +2,24 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +import weakref +from typing import ( + IO, + TYPE_CHECKING, + Any, + Type, + Union, + Generic, + TypeVar, + Callable, + Iterable, + Optional, + AsyncIterable, + cast, +) from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, @@ -19,7 +34,6 @@ ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( @@ -50,7 +64,7 @@ strip_annotated_type, ) from ._compat import ( - PYDANTIC_V2, + PYDANTIC_V1, ConfigDict, GenericModel as BaseGenericModel, get_args, @@ -65,7 +79,7 @@ from ._constants import RAW_RESPONSE_HEADER if TYPE_CHECKING: - from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema + from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema __all__ = ["BaseModel", "GenericModel"] @@ -81,11 +95,7 @@ class _ConfigProtocol(Protocol): class BaseModel(pydantic.BaseModel): - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict( - extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) - ) - else: + if PYDANTIC_V1: @property @override @@ -95,6 +105,10 @@ def model_fields_set(self) -> set[str]: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] extra: Any = pydantic.Extra.allow # type: ignore + else: + model_config: ClassVar[ConfigDict] = ConfigDict( + extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + ) def to_dict( self, @@ -172,7 +186,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. @@ -208,28 +222,32 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] else: fields_values[name] = field_get_default(field) + extra_field_type = _get_extra_fields_type(__cls) + _extra = {} for key, value in values.items(): if key not in model_fields: - if PYDANTIC_V2: - _extra[key] = value - else: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + + if PYDANTIC_V1: _fields_set.add(key) - fields_values[key] = value + fields_values[key] = parsed + else: + _extra[key] = parsed object.__setattr__(m, "__dict__", fields_values) - if PYDANTIC_V2: - # these properties are copied from Pydantic's `model_construct()` method - object.__setattr__(m, "__pydantic_private__", None) - object.__setattr__(m, "__pydantic_extra__", _extra) - object.__setattr__(m, "__pydantic_fields_set__", _fields_set) - else: + if PYDANTIC_V1: # init_private_attributes() does not exist in v2 m._init_private_attributes() # type: ignore # copied from Pydantic v1's `construct()` method object.__setattr__(m, "__fields_set__", _fields_set) + else: + # these properties are copied from Pydantic's `model_construct()` method + object.__setattr__(m, "__pydantic_private__", None) + object.__setattr__(m, "__pydantic_extra__", _extra) + object.__setattr__(m, "__pydantic_fields_set__", _fields_set) return m @@ -239,7 +257,7 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] # although not in practice model_construct = construct - if not PYDANTIC_V2: + if PYDANTIC_V1: # we define aliases for some of the new pydantic v2 methods so # that we can just document these methods without having to specify # a specific pydantic version as some users may not know which @@ -252,13 +270,15 @@ def model_dump( mode: Literal["json", "python"] | str = "python", include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + context: Any | None = None, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, - context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -267,16 +287,24 @@ def model_dump( Args: mode: The mode in which `to_python` should run. - If mode is 'json', the dictionary will only contain JSON serializable types. - If mode is 'python', the dictionary may contain any Python objects. - include: A list of fields to include in the output. - exclude: A list of fields to exclude from the output. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. by_alias: Whether to use the field's alias in the dictionary key if defined. - exclude_unset: Whether to exclude fields that are unset or None from the output. - exclude_defaults: Whether to exclude fields that are set to their default value from the output. - exclude_none: Whether to exclude fields that have a value of `None` from the output. - round_trip: Whether to enable serialization and deserialization round-trip support. - warnings: Whether to log warnings when invalid fields are encountered. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. Returns: A dictionary representation of the model. @@ -291,31 +319,38 @@ def model_dump( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, ) - return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped @override def model_dump_json( self, *, indent: int | None = None, + ensure_ascii: bool = False, include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + context: Any | None = None, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, - context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> str: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json @@ -344,11 +379,17 @@ def model_dump_json( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if ensure_ascii != False: + raise ValueError("ensure_ascii is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") return super().json( # type: ignore[reportDeprecated] indent=indent, include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, @@ -359,15 +400,32 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) - if PYDANTIC_V2: - type_ = field.annotation - else: + if PYDANTIC_V1: type_ = cast(type, field.outer_type_) # type: ignore + else: + type_ = field.annotation # type: ignore if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") - return construct_type(value=value, type_=type_) + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) + + +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if PYDANTIC_V1: + # TODO + return None + + schema = cls.__pydantic_core_schema__ + if schema["type"] == "model": + fields = schema["schema"] + if fields["type"] == "model-fields": + extras = fields.get("extras_schema") + if extras and "cls" in extras: + # mypy can't narrow the type + return extras["cls"] # type: ignore[no-any-return] + + return None def is_basemodel(type_: type) -> bool: @@ -421,20 +479,28 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: return cast(_T, construct_type(value=value, type_=type_)) -def construct_type(*, value: object, type_: object) -> object: +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. """ + + # store a reference to the original type we were given before we extract any inner + # types so that we can properly resolve forward references in `TypeAliasType` annotations + original_type = None + # we allow `object` as the input type because otherwise, passing things like # `Literal['value']` will be reported as a type error by type checkers type_ = cast("type[object]", type_) if is_type_alias_type(type_): + original_type = type_ # type: ignore[unreachable] type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - meta: tuple[Any, ...] = get_args(type_)[1:] + if metadata is not None and len(metadata) > 0: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) else: meta = tuple() @@ -446,7 +512,7 @@ def construct_type(*, value: object, type_: object) -> object: if is_union(origin): try: - return validate_type(type_=cast("type[object]", type_), value=value) + return validate_type(type_=cast("type[object]", original_type or type_), value=value) except Exception: pass @@ -538,6 +604,9 @@ class CachedDiscriminatorType(Protocol): __discriminator__: DiscriminatorDetails +DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary() + + class DiscriminatorDetails: field_name: str """The name of the discriminator field in the variant class, e.g. @@ -580,8 +649,9 @@ def __init__( def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: - if isinstance(union, CachedDiscriminatorType): - return union.__discriminator__ + cached = DISCRIMINATOR_CACHE.get(union) + if cached is not None: + return cached discriminator_field_name: str | None = None @@ -599,30 +669,30 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, for variant in get_args(union): variant = strip_annotated_type(variant) if is_basemodel_type(variant): - if PYDANTIC_V2: - field = _extract_field_schema_pv2(variant, discriminator_field_name) - if not field: + if PYDANTIC_V1: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field.get("serialization_alias") - - field_schema = field["schema"] + discriminator_alias = field_info.alias - if field_schema["type"] == "literal": - for entry in cast("LiteralSchema", field_schema)["expected"]: + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant else: - field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - if not field_info: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field_info.alias + discriminator_alias = field.get("serialization_alias") + + field_schema = field["schema"] - if field_info.annotation and is_literal_type(field_info.annotation): - for entry in get_args(field_info.annotation): + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: if isinstance(entry, str): mapping[entry] = variant @@ -634,21 +704,24 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, discriminator_field=discriminator_field_name, discriminator_alias=discriminator_alias, ) - cast(CachedDiscriminatorType, union).__discriminator__ = details + DISCRIMINATOR_CACHE.setdefault(union, details) return details def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: schema = model.__pydantic_core_schema__ + if schema["type"] == "definitions": + schema = schema["schema"] + if schema["type"] != "model": return None + schema = cast("ModelSchema", schema) fields_schema = schema["schema"] if fields_schema["type"] != "model-fields": return None fields_schema = cast("ModelFieldsSchema", fields_schema) - field = fields_schema["fields"].get(field_name) if not field: return None @@ -672,7 +745,7 @@ def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: setattr(typ, "__pydantic_config__", config) # noqa: B010 -# our use of subclasssing here causes weirdness for type checkers, +# our use of subclassing here causes weirdness for type checkers, # so we just pretend that we don't subclass if TYPE_CHECKING: GenericModel = BaseModel @@ -682,7 +755,7 @@ class GenericModel(BaseGenericModel, BaseModel): pass -if PYDANTIC_V2: +if not PYDANTIC_V1: from pydantic import TypeAdapter as _TypeAdapter _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) @@ -727,8 +800,10 @@ class FinalRequestOptionsInput(TypedDict, total=False): timeout: float | Timeout | None files: HttpxRequestFiles | None idempotency_key: str + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] json_data: Body extra_json: AnyMapping + follow_redirects: bool @final @@ -742,18 +817,20 @@ class FinalRequestOptions(pydantic.BaseModel): files: Union[HttpxRequestFiles, None] = None idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. json_data: Union[Body, None] = None extra_json: Union[AnyMapping, None] = None - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - else: + if PYDANTIC_V1: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] arbitrary_types_allowed: bool = True + else: + model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) def get_max_retries(self, max_retries: int) -> int: if isinstance(self.max_retries, NotGiven): @@ -786,9 +863,9 @@ def construct( # type: ignore key: strip_not_given(value) for key, value in values.items() } - if PYDANTIC_V2: - return super().model_construct(_fields_set, **kwargs) - return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + return super().model_construct(_fields_set, **kwargs) if not TYPE_CHECKING: # type checkers incorrectly complain about this assignment diff --git a/src/mixedbread/_qs.py b/src/mixedbread/_qs.py index 274320ca..ada6fd3f 100644 --- a/src/mixedbread/_qs.py +++ b/src/mixedbread/_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/mixedbread/_response.py b/src/mixedbread/_response.py index 3221a254..50547a3b 100644 --- a/src/mixedbread/_response.py +++ b/src/mixedbread/_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): @@ -150,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -160,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -173,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -195,8 +200,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`") @@ -210,7 +213,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError( "Pydantic models must subclass our base model type, e.g. `from mixedbread import BaseModel`" ) @@ -229,7 +238,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() diff --git a/src/mixedbread/_streaming.py b/src/mixedbread/_streaming.py index e5834ace..235a94a8 100644 --- a/src/mixedbread/_streaming.py +++ b/src/mixedbread/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import Mixedbread, AsyncMixedbread + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,10 +32,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: Mixedbread, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -54,12 +57,12 @@ def __stream__(self) -> Iterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # Ensure the entire stream is consumed - for _sse in iterator: - ... + try: + for sse in iterator: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() def __enter__(self) -> Self: return self @@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -94,10 +97,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncMixedbread, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -118,12 +123,12 @@ async def __stream__(self) -> AsyncIterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - async for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # Ensure the entire stream is consumed - async for _sse in iterator: - ... + try: + async for sse in iterator: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() async def __aenter__(self) -> Self: return self diff --git a/src/mixedbread/_types.py b/src/mixedbread/_types.py index b1e781f8..fa2086c9 100644 --- a/src/mixedbread/_types.py +++ b/src/mixedbread/_types.py @@ -13,10 +13,23 @@ Mapping, TypeVar, Callable, + Iterable, + Iterator, Optional, Sequence, + AsyncIterable, +) +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, ) -from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable import httpx import pydantic @@ -45,6 +58,13 @@ else: Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + FileTypes = Union[ # file (or bytes) FileContent, @@ -100,23 +120,27 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool # Sentinel class used until PEP 0661 is accepted class NotGiven: """ - A sentinel singleton class used to distinguish omitted keyword arguments - from those passed in with the value None (which may have different behavior). + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. For example: ```py - def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ... + def create(timeout: Timeout | None | NotGiven = not_given): ... - get(timeout=1) # 1s timeout - get(timeout=None) # No timeout - get() # Default timeout behavior, which may not be statically known at the method definition. + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior ``` """ @@ -128,13 +152,14 @@ def __repr__(self) -> str: return "NOT_GIVEN" -NotGivenOr = Union[_T, NotGiven] +not_given = NotGiven() +# for backwards compatibility: NOT_GIVEN = NotGiven() class Omit: - """In certain situations you need to be able to represent a case where a default value has - to be explicitly removed and `None` is not an appropriate substitute, for example: + """ + To explicitly omit something from being sent in a request, use `omit`. ```py # as the default `Content-Type` header is `application/json` that will be sent @@ -144,8 +169,8 @@ class Omit: # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' client.post(..., headers={"Content-Type": "multipart/form-data"}) - # instead you can remove the default `application/json` header by passing Omit - client.post(..., headers={"Content-Type": Omit()}) + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) ``` """ @@ -153,6 +178,9 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod @@ -215,3 +243,28 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool + + +_T_co = TypeVar("_T_co", covariant=True) + + +if TYPE_CHECKING: + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. + class SequenceNotStr(Protocol[_T_co]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... + @overload + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... + def __contains__(self, value: object, /) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_co]: ... + def __reversed__(self) -> Iterator[_T_co]: ... +else: + # just point this to a normal `Sequence` at runtime to avoid having to special case + # deserializing our custom sequence type + SequenceNotStr = Sequence diff --git a/src/mixedbread/_utils/__init__.py b/src/mixedbread/_utils/__init__.py index d4fda26f..dc64e29a 100644 --- a/src/mixedbread/_utils/__init__.py +++ b/src/mixedbread/_utils/__init__.py @@ -10,7 +10,6 @@ lru_cache as lru_cache, is_mapping as is_mapping, is_tuple_t as is_tuple_t, - parse_date as parse_date, is_iterable as is_iterable, is_sequence as is_sequence, coerce_float as coerce_float, @@ -23,7 +22,6 @@ coerce_boolean as coerce_boolean, coerce_integer as coerce_integer, file_from_path as file_from_path, - parse_datetime as parse_datetime, strip_not_given as strip_not_given, deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, @@ -32,12 +30,20 @@ maybe_coerce_boolean as maybe_coerce_boolean, maybe_coerce_integer as maybe_coerce_integer, ) +from ._compat import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, +) from ._typing import ( is_list_type as is_list_type, is_union_type as is_union_type, extract_type_arg as extract_type_arg, is_iterable_type as is_iterable_type, is_required_type as is_required_type, + is_sequence_type as is_sequence_type, is_annotated_type as is_annotated_type, is_type_alias_type as is_type_alias_type, strip_annotated_type as strip_annotated_type, @@ -55,3 +61,4 @@ function_has_argument as function_has_argument, assert_signatures_in_sync as assert_signatures_in_sync, ) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/mixedbread/_utils/_compat.py b/src/mixedbread/_utils/_compat.py new file mode 100644 index 00000000..2c70b299 --- /dev/null +++ b/src/mixedbread/_utils/_compat.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import sys +import typing_extensions +from typing import Any, Type, Union, Literal, Optional +from datetime import date, datetime +from typing_extensions import get_args as _get_args, get_origin as _get_origin + +from .._types import StrBytesIntFloat +from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime + +_LITERAL_TYPES = {Literal, typing_extensions.Literal} + + +def get_args(tp: type[Any]) -> tuple[Any, ...]: + return _get_args(tp) + + +def get_origin(tp: type[Any]) -> type[Any] | None: + return _get_origin(tp) + + +def is_union(tp: Optional[Type[Any]]) -> bool: + if sys.version_info < (3, 10): + return tp is Union # type: ignore[comparison-overlap] + else: + import types + + return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] + + +def is_typeddict(tp: Type[Any]) -> bool: + return typing_extensions.is_typeddict(tp) + + +def is_literal_type(tp: Type[Any]) -> bool: + return get_origin(tp) in _LITERAL_TYPES + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + return _parse_date(value) + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + return _parse_datetime(value) diff --git a/src/mixedbread/_utils/_datetime_parse.py b/src/mixedbread/_utils/_datetime_parse.py new file mode 100644 index 00000000..7cb9d9e6 --- /dev/null +++ b/src/mixedbread/_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/mixedbread/_utils/_json.py b/src/mixedbread/_utils/_json.py new file mode 100644 index 00000000..60584214 --- /dev/null +++ b/src/mixedbread/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/src/mixedbread/_utils/_proxy.py b/src/mixedbread/_utils/_proxy.py index ffd883e9..0f239a33 100644 --- a/src/mixedbread/_utils/_proxy.py +++ b/src/mixedbread/_utils/_proxy.py @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ diff --git a/src/mixedbread/_utils/_resources_proxy.py b/src/mixedbread/_utils/_resources_proxy.py new file mode 100644 index 00000000..ad97bb05 --- /dev/null +++ b/src/mixedbread/_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 `mixedbread.resources` module. + + This is used so that we can lazily import `mixedbread.resources` only when + needed *and* so that users can just import `mixedbread` and reference `mixedbread.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("mixedbread.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() diff --git a/src/mixedbread/_utils/_sync.py b/src/mixedbread/_utils/_sync.py index 8b3aaf2b..f6027c18 100644 --- a/src/mixedbread/_utils/_sync.py +++ b/src/mixedbread/_utils/_sync.py @@ -1,47 +1,34 @@ 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 +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 -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. +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) - 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) + return await anyio.to_thread.run_sync( + functools.partial(func, *args, **kwargs), + ) # inspired by `asyncer`, https://github.com/tiangolo/asyncer def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ Take a blocking function and create an async one that receives the same - positional and keyword arguments. For python version 3.9 and above, it uses - asyncio.to_thread to run the function in a separate thread. For python version - 3.8, it uses locally defined copy of the asyncio.to_thread function which was - introduced in python 3.9. + positional and keyword arguments. Usage: diff --git a/src/mixedbread/_utils/_transform.py b/src/mixedbread/_utils/_transform.py index a6b62cad..52075492 100644 --- a/src/mixedbread/_utils/_transform.py +++ b/src/mixedbread/_utils/_transform.py @@ -5,27 +5,31 @@ import pathlib from typing import Any, Mapping, TypeVar, cast from datetime import date, datetime -from typing_extensions import Literal, get_args, override, get_type_hints +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints import anyio import pydantic from ._utils import ( is_list, + is_given, + lru_cache, is_mapping, is_iterable, + is_sequence, ) from .._files import is_base64_file_input +from ._compat import get_origin, is_typeddict from ._typing import ( is_list_type, is_union_type, extract_type_arg, is_iterable_type, is_required_type, + is_sequence_type, is_annotated_type, strip_annotated_type, ) -from .._compat import model_dump, is_typeddict _T = TypeVar("_T") @@ -108,6 +112,7 @@ class Params(TypedDict, total=False): return cast(_T, transformed) +@lru_cache(maxsize=8096) def _get_annotated_type(type_: type) -> type | None: """If the given type is an `Annotated` type then it is returned, if not `None` is returned. @@ -126,7 +131,7 @@ def _get_annotated_type(type_: type) -> type | None: def _maybe_transform_key(key: str, type_: type) -> str: """Transform the given `data` based on the annotations provided in `type_`. - Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata. + Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata. """ annotated_type = _get_annotated_type(type_) if annotated_type is None: @@ -142,6 +147,10 @@ def _maybe_transform_key(key: str, type_: type) -> str: return key +def _no_transform_needed(annotation: type) -> bool: + return annotation == float or annotation == int + + def _transform_recursive( data: object, *, @@ -160,18 +169,27 @@ def _transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return _transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. @@ -179,6 +197,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): @@ -240,6 +267,11 @@ def _transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -303,18 +335,27 @@ async def _async_transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return await _async_transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. @@ -322,6 +363,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): @@ -383,6 +433,11 @@ async def _async_transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -390,3 +445,13 @@ async def _async_transform_typeddict( else: result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/mixedbread/_utils/_typing.py b/src/mixedbread/_utils/_typing.py index 278749b1..193109f3 100644 --- a/src/mixedbread/_utils/_typing.py +++ b/src/mixedbread/_utils/_typing.py @@ -13,8 +13,9 @@ get_origin, ) +from ._utils import lru_cache from .._types import InheritsGeneric -from .._compat import is_union as _is_union +from ._compat import is_union as _is_union def is_annotated_type(typ: type) -> bool: @@ -25,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 @@ -66,6 +72,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])) @@ -108,7 +115,7 @@ class MyResponse(Foo[_T]): ``` """ cls = cast(object, get_origin(typ) or typ) - if cls in generic_bases: + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] # we're given the class directly return extract_type_arg(typ, index) diff --git a/src/mixedbread/_utils/_utils.py b/src/mixedbread/_utils/_utils.py index e5811bba..eec7f4a1 100644 --- a/src/mixedbread/_utils/_utils.py +++ b/src/mixedbread/_utils/_utils.py @@ -21,8 +21,7 @@ import sniffio -from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike -from .._compat import parse_date as parse_date, parse_datetime as parse_datetime +from .._types import Omit, NotGiven, FileTypes, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -64,7 +63,7 @@ def _extract_items( try: key = path[index] except IndexError: - if isinstance(obj, NotGiven): + if not is_given(obj): # no value was provided - we can safely ignore return [] @@ -72,8 +71,16 @@ def _extract_items( from .._files import assert_is_file_content # We have exhausted the path, return the entry we found. - assert_is_file_content(obj, key=flattened_key) assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for entry in obj: + assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") + files.append((flattened_key + "[]", cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) return [(flattened_key, cast(FileTypes, obj))] index += 1 @@ -119,14 +126,14 @@ def _extract_items( return [] -def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]: - return not isinstance(obj, NotGiven) +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) # Type safe methods for narrowing types with TypeVars. # The default narrowing for isinstance(obj, dict) is dict[unknown, unknown], # however this cause Pyright to rightfully report errors. As we know we don't -# care about the contained types we can safely use `object` in it's place. +# care about the contained types we can safely use `object` in its place. # # There are two separate functions defined, `is_*` and `is_*_t` for different use cases. # `is_*` is for when you're dealing with an unknown input diff --git a/src/mixedbread/_version.py b/src/mixedbread/_version.py index c367ca09..a6b0880b 100644 --- a/src/mixedbread/_version.py +++ b/src/mixedbread/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "mixedbread" -__version__ = "0.1.0-alpha.11" +__version__ = "1.0.0" # x-release-please-version diff --git a/src/mixedbread/pagination.py b/src/mixedbread/pagination.py new file mode 100644 index 00000000..a983df1e --- /dev/null +++ b/src/mixedbread/pagination.py @@ -0,0 +1,194 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Generic, TypeVar, Optional +from typing_extensions import override + +from ._models import BaseModel +from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage + +__all__ = [ + "LimitOffsetPagination", + "SyncLimitOffset", + "AsyncLimitOffset", + "CursorPagination", + "SyncCursor", + "AsyncCursor", +] + +_T = TypeVar("_T") + + +class LimitOffsetPagination(BaseModel): + total: Optional[int] = None + + offset: Optional[int] = None + + +class SyncLimitOffset(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + pagination: Optional[LimitOffsetPagination] = None + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = None + if self.pagination is not None: + if self.pagination.offset is not None: + offset = self.pagination.offset + if offset is None: + return None # type: ignore[unreachable] + + length = len(self._get_page_items()) + current_count = offset + length + + total = None + if self.pagination is not None: + if self.pagination.total is not None: + total = self.pagination.total + if total is None: + return None + + if current_count < total: + return PageInfo(params={"offset": current_count}) + + return None + + +class AsyncLimitOffset(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + pagination: Optional[LimitOffsetPagination] = None + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = None + if self.pagination is not None: + if self.pagination.offset is not None: + offset = self.pagination.offset + if offset is None: + return None # type: ignore[unreachable] + + length = len(self._get_page_items()) + current_count = offset + length + + total = None + if self.pagination is not None: + if self.pagination.total is not None: + total = self.pagination.total + if total is None: + return None + + if current_count < total: + return PageInfo(params={"offset": current_count}) + + return None + + +class CursorPagination(BaseModel): + first_cursor: Optional[str] = None + + last_cursor: Optional[str] = None + + has_more: Optional[bool] = None + + total: Optional[int] = None + + +class SyncCursor(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + pagination: Optional[CursorPagination] = None + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def has_next_page(self) -> bool: + has_more = None + if self.pagination is not None: + if self.pagination.has_more is not None: + has_more = self.pagination.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + + @override + def next_page_info(self) -> Optional[PageInfo]: + if self._options.params.get("before"): + first_cursor = None + if self.pagination is not None: + if self.pagination.first_cursor is not None: + first_cursor = self.pagination.first_cursor + if not first_cursor: + return None + + return PageInfo(params={"before": first_cursor}) + + last_cursor = None + if self.pagination is not None: + if self.pagination.last_cursor is not None: + last_cursor = self.pagination.last_cursor + if not last_cursor: + return None + + return PageInfo(params={"after": last_cursor}) + + +class AsyncCursor(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + pagination: Optional[CursorPagination] = None + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def has_next_page(self) -> bool: + has_more = None + if self.pagination is not None: + if self.pagination.has_more is not None: + has_more = self.pagination.has_more + if has_more is not None and has_more is False: + return False + + return super().has_next_page() + + @override + def next_page_info(self) -> Optional[PageInfo]: + if self._options.params.get("before"): + first_cursor = None + if self.pagination is not None: + if self.pagination.first_cursor is not None: + first_cursor = self.pagination.first_cursor + if not first_cursor: + return None + + return PageInfo(params={"before": first_cursor}) + + last_cursor = None + if self.pagination is not None: + if self.pagination.last_cursor is not None: + last_cursor = self.pagination.last_cursor + if not last_cursor: + return None + + return PageInfo(params={"after": last_cursor}) diff --git a/src/mixedbread/resources/__init__.py b/src/mixedbread/resources/__init__.py index 3dda4b1c..a9073d39 100644 --- a/src/mixedbread/resources/__init__.py +++ b/src/mixedbread/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .chat import ( + ChatResource, + AsyncChatResource, + ChatResourceWithRawResponse, + AsyncChatResourceWithRawResponse, + ChatResourceWithStreamingResponse, + AsyncChatResourceWithStreamingResponse, +) from .files import ( FilesResource, AsyncFilesResource, @@ -8,6 +16,14 @@ FilesResourceWithStreamingResponse, AsyncFilesResourceWithStreamingResponse, ) +from .stores import ( + StoresResource, + AsyncStoresResource, + StoresResourceWithRawResponse, + AsyncStoresResourceWithRawResponse, + StoresResourceWithStreamingResponse, + AsyncStoresResourceWithStreamingResponse, +) from .parsing import ( ParsingResource, AsyncParsingResource, @@ -16,13 +32,13 @@ ParsingResourceWithStreamingResponse, AsyncParsingResourceWithStreamingResponse, ) -from .reranking import ( - RerankingResource, - AsyncRerankingResource, - RerankingResourceWithRawResponse, - AsyncRerankingResourceWithRawResponse, - RerankingResourceWithStreamingResponse, - AsyncRerankingResourceWithStreamingResponse, +from .api_keys import ( + APIKeysResource, + AsyncAPIKeysResource, + APIKeysResourceWithRawResponse, + AsyncAPIKeysResourceWithRawResponse, + APIKeysResourceWithStreamingResponse, + AsyncAPIKeysResourceWithStreamingResponse, ) from .embeddings import ( EmbeddingsResource, @@ -32,14 +48,6 @@ EmbeddingsResourceWithStreamingResponse, AsyncEmbeddingsResourceWithStreamingResponse, ) -from .completions import ( - CompletionsResource, - AsyncCompletionsResource, - CompletionsResourceWithRawResponse, - AsyncCompletionsResourceWithRawResponse, - CompletionsResourceWithStreamingResponse, - AsyncCompletionsResourceWithStreamingResponse, -) from .extractions import ( ExtractionsResource, AsyncExtractionsResource, @@ -48,54 +56,34 @@ ExtractionsResourceWithStreamingResponse, AsyncExtractionsResourceWithStreamingResponse, ) -from .service_info import ( - ServiceInfoResource, - AsyncServiceInfoResource, - ServiceInfoResourceWithRawResponse, - AsyncServiceInfoResourceWithRawResponse, - ServiceInfoResourceWithStreamingResponse, - AsyncServiceInfoResourceWithStreamingResponse, -) -from .vector_stores import ( - VectorStoresResource, - AsyncVectorStoresResource, - VectorStoresResourceWithRawResponse, - AsyncVectorStoresResourceWithRawResponse, - VectorStoresResourceWithStreamingResponse, - AsyncVectorStoresResourceWithStreamingResponse, +from .data_sources import ( + DataSourcesResource, + AsyncDataSourcesResource, + DataSourcesResourceWithRawResponse, + AsyncDataSourcesResourceWithRawResponse, + DataSourcesResourceWithStreamingResponse, + AsyncDataSourcesResourceWithStreamingResponse, ) __all__ = [ - "ServiceInfoResource", - "AsyncServiceInfoResource", - "ServiceInfoResourceWithRawResponse", - "AsyncServiceInfoResourceWithRawResponse", - "ServiceInfoResourceWithStreamingResponse", - "AsyncServiceInfoResourceWithStreamingResponse", - "FilesResource", - "AsyncFilesResource", - "FilesResourceWithRawResponse", - "AsyncFilesResourceWithRawResponse", - "FilesResourceWithStreamingResponse", - "AsyncFilesResourceWithStreamingResponse", - "CompletionsResource", - "AsyncCompletionsResource", - "CompletionsResourceWithRawResponse", - "AsyncCompletionsResourceWithRawResponse", - "CompletionsResourceWithStreamingResponse", - "AsyncCompletionsResourceWithStreamingResponse", - "VectorStoresResource", - "AsyncVectorStoresResource", - "VectorStoresResourceWithRawResponse", - "AsyncVectorStoresResourceWithRawResponse", - "VectorStoresResourceWithStreamingResponse", - "AsyncVectorStoresResourceWithStreamingResponse", + "StoresResource", + "AsyncStoresResource", + "StoresResourceWithRawResponse", + "AsyncStoresResourceWithRawResponse", + "StoresResourceWithStreamingResponse", + "AsyncStoresResourceWithStreamingResponse", "ParsingResource", "AsyncParsingResource", "ParsingResourceWithRawResponse", "AsyncParsingResourceWithRawResponse", "ParsingResourceWithStreamingResponse", "AsyncParsingResourceWithStreamingResponse", + "FilesResource", + "AsyncFilesResource", + "FilesResourceWithRawResponse", + "AsyncFilesResourceWithRawResponse", + "FilesResourceWithStreamingResponse", + "AsyncFilesResourceWithStreamingResponse", "ExtractionsResource", "AsyncExtractionsResource", "ExtractionsResourceWithRawResponse", @@ -108,10 +96,22 @@ "AsyncEmbeddingsResourceWithRawResponse", "EmbeddingsResourceWithStreamingResponse", "AsyncEmbeddingsResourceWithStreamingResponse", - "RerankingResource", - "AsyncRerankingResource", - "RerankingResourceWithRawResponse", - "AsyncRerankingResourceWithRawResponse", - "RerankingResourceWithStreamingResponse", - "AsyncRerankingResourceWithStreamingResponse", + "DataSourcesResource", + "AsyncDataSourcesResource", + "DataSourcesResourceWithRawResponse", + "AsyncDataSourcesResourceWithRawResponse", + "DataSourcesResourceWithStreamingResponse", + "AsyncDataSourcesResourceWithStreamingResponse", + "APIKeysResource", + "AsyncAPIKeysResource", + "APIKeysResourceWithRawResponse", + "AsyncAPIKeysResourceWithRawResponse", + "APIKeysResourceWithStreamingResponse", + "AsyncAPIKeysResourceWithStreamingResponse", + "ChatResource", + "AsyncChatResource", + "ChatResourceWithRawResponse", + "AsyncChatResourceWithRawResponse", + "ChatResourceWithStreamingResponse", + "AsyncChatResourceWithStreamingResponse", ] diff --git a/src/mixedbread/resources/api_keys.py b/src/mixedbread/resources/api_keys.py new file mode 100644 index 00000000..66408456 --- /dev/null +++ b/src/mixedbread/resources/api_keys.py @@ -0,0 +1,691 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from datetime import datetime + +import httpx + +from ..types import api_key_list_params, api_key_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncLimitOffset, AsyncLimitOffset +from .._base_client import AsyncPaginator, make_request_options +from ..types.api_key import APIKey +from ..types.api_key_created import APIKeyCreated +from ..types.api_key_delete_response import APIKeyDeleteResponse + +__all__ = ["APIKeysResource", "AsyncAPIKeysResource"] + + +class APIKeysResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> APIKeysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return APIKeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> APIKeysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return APIKeysResourceWithStreamingResponse(self) + + def create( + self, + *, + name: str | Omit = omit, + scope: Optional[Iterable[api_key_create_params.Scope]] | 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, + ) -> APIKeyCreated: + """ + Create a new API key. + + Args: params: The parameters for creating the API key. + + Returns: ApiKeyCreated: The response containing the details of the created API + key. + + Args: + name: A name/description for the API key + + scope: The scope of the API key + + expires_at: Optional expiration datetime + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/api-keys", + body=maybe_transform( + { + "name": name, + "scope": scope, + "expires_at": expires_at, + }, + api_key_create_params.APIKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKeyCreated, + ) + + def retrieve( + self, + api_key_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, + ) -> APIKey: + """ + Retrieve details of a specific API key by its ID. + + Args: api_key_id: The ID of the API key to retrieve. + + Returns: ApiKey: The response containing the API key details. + + Args: + api_key_id: The ID of the API key to retrieve + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return self._get( + f"/v1/api-keys/{api_key_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKey, + ) + + def list( + self, + *, + limit: 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, + ) -> SyncLimitOffset[APIKey]: + """ + List all API keys for the authenticated user. + + Args: pagination: The pagination options + + Returns: A list of API keys belonging to the user. + + Args: + limit: Maximum number of items to return per page + + offset: Offset of the first item to return + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/api-keys", + page=SyncLimitOffset[APIKey], + 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, + }, + api_key_list_params.APIKeyListParams, + ), + ), + model=APIKey, + ) + + def delete( + self, + api_key_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, + ) -> APIKeyDeleteResponse: + """ + Delete a specific API key by its ID. + + Args: api_key_id: The ID of the API key to delete. + + Returns: ApiKeyDeleted: The response containing the details of the deleted API + key. + + Args: + api_key_id: The ID of the API key to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return self._delete( + f"/v1/api-keys/{api_key_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKeyDeleteResponse, + ) + + def reroll( + self, + api_key_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, + ) -> APIKeyCreated: + """ + Reroll the secret for a specific API key by its ID. + + This generates a new secret key, invalidating the old one. + + Args: api_key_id: The ID of the API key to reroll. + + Returns: ApiKeyCreated: The response containing the API key details with the new + secret key. + + Args: + api_key_id: The ID of the API key to reroll + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return self._post( + f"/v1/api-keys/{api_key_id}/reroll", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKeyCreated, + ) + + def revoke( + self, + api_key_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, + ) -> APIKey: + """ + Revoke a specific API key by its ID. + + Args: api_key_id: The ID of the API key to revoke. + + Returns: ApiKey: The response containing the details of the revoked API key. + + Args: + api_key_id: The ID of the API key to revoke + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return self._post( + f"/v1/api-keys/{api_key_id}/revoke", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKey, + ) + + +class AsyncAPIKeysResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAPIKeysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return AsyncAPIKeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAPIKeysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return AsyncAPIKeysResourceWithStreamingResponse(self) + + async def create( + self, + *, + name: str | Omit = omit, + scope: Optional[Iterable[api_key_create_params.Scope]] | 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, + ) -> APIKeyCreated: + """ + Create a new API key. + + Args: params: The parameters for creating the API key. + + Returns: ApiKeyCreated: The response containing the details of the created API + key. + + Args: + name: A name/description for the API key + + scope: The scope of the API key + + expires_at: Optional expiration datetime + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/api-keys", + body=await async_maybe_transform( + { + "name": name, + "scope": scope, + "expires_at": expires_at, + }, + api_key_create_params.APIKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKeyCreated, + ) + + async def retrieve( + self, + api_key_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, + ) -> APIKey: + """ + Retrieve details of a specific API key by its ID. + + Args: api_key_id: The ID of the API key to retrieve. + + Returns: ApiKey: The response containing the API key details. + + Args: + api_key_id: The ID of the API key to retrieve + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return await self._get( + f"/v1/api-keys/{api_key_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKey, + ) + + def list( + self, + *, + limit: 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, + ) -> AsyncPaginator[APIKey, AsyncLimitOffset[APIKey]]: + """ + List all API keys for the authenticated user. + + Args: pagination: The pagination options + + Returns: A list of API keys belonging to the user. + + Args: + limit: Maximum number of items to return per page + + offset: Offset of the first item to return + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/api-keys", + page=AsyncLimitOffset[APIKey], + 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, + }, + api_key_list_params.APIKeyListParams, + ), + ), + model=APIKey, + ) + + async def delete( + self, + api_key_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, + ) -> APIKeyDeleteResponse: + """ + Delete a specific API key by its ID. + + Args: api_key_id: The ID of the API key to delete. + + Returns: ApiKeyDeleted: The response containing the details of the deleted API + key. + + Args: + api_key_id: The ID of the API key to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return await self._delete( + f"/v1/api-keys/{api_key_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKeyDeleteResponse, + ) + + async def reroll( + self, + api_key_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, + ) -> APIKeyCreated: + """ + Reroll the secret for a specific API key by its ID. + + This generates a new secret key, invalidating the old one. + + Args: api_key_id: The ID of the API key to reroll. + + Returns: ApiKeyCreated: The response containing the API key details with the new + secret key. + + Args: + api_key_id: The ID of the API key to reroll + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return await self._post( + f"/v1/api-keys/{api_key_id}/reroll", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKeyCreated, + ) + + async def revoke( + self, + api_key_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, + ) -> APIKey: + """ + Revoke a specific API key by its ID. + + Args: api_key_id: The ID of the API key to revoke. + + Returns: ApiKey: The response containing the details of the revoked API key. + + Args: + api_key_id: The ID of the API key to revoke + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 api_key_id: + raise ValueError(f"Expected a non-empty value for `api_key_id` but received {api_key_id!r}") + return await self._post( + f"/v1/api-keys/{api_key_id}/revoke", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=APIKey, + ) + + +class APIKeysResourceWithRawResponse: + def __init__(self, api_keys: APIKeysResource) -> None: + self._api_keys = api_keys + + self.create = to_raw_response_wrapper( + api_keys.create, + ) + self.retrieve = to_raw_response_wrapper( + api_keys.retrieve, + ) + self.list = to_raw_response_wrapper( + api_keys.list, + ) + self.delete = to_raw_response_wrapper( + api_keys.delete, + ) + self.reroll = to_raw_response_wrapper( + api_keys.reroll, + ) + self.revoke = to_raw_response_wrapper( + api_keys.revoke, + ) + + +class AsyncAPIKeysResourceWithRawResponse: + def __init__(self, api_keys: AsyncAPIKeysResource) -> None: + self._api_keys = api_keys + + self.create = async_to_raw_response_wrapper( + api_keys.create, + ) + self.retrieve = async_to_raw_response_wrapper( + api_keys.retrieve, + ) + self.list = async_to_raw_response_wrapper( + api_keys.list, + ) + self.delete = async_to_raw_response_wrapper( + api_keys.delete, + ) + self.reroll = async_to_raw_response_wrapper( + api_keys.reroll, + ) + self.revoke = async_to_raw_response_wrapper( + api_keys.revoke, + ) + + +class APIKeysResourceWithStreamingResponse: + def __init__(self, api_keys: APIKeysResource) -> None: + self._api_keys = api_keys + + self.create = to_streamed_response_wrapper( + api_keys.create, + ) + self.retrieve = to_streamed_response_wrapper( + api_keys.retrieve, + ) + self.list = to_streamed_response_wrapper( + api_keys.list, + ) + self.delete = to_streamed_response_wrapper( + api_keys.delete, + ) + self.reroll = to_streamed_response_wrapper( + api_keys.reroll, + ) + self.revoke = to_streamed_response_wrapper( + api_keys.revoke, + ) + + +class AsyncAPIKeysResourceWithStreamingResponse: + def __init__(self, api_keys: AsyncAPIKeysResource) -> None: + self._api_keys = api_keys + + self.create = async_to_streamed_response_wrapper( + api_keys.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + api_keys.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + api_keys.list, + ) + self.delete = async_to_streamed_response_wrapper( + api_keys.delete, + ) + self.reroll = async_to_streamed_response_wrapper( + api_keys.reroll, + ) + self.revoke = async_to_streamed_response_wrapper( + api_keys.revoke, + ) diff --git a/src/mixedbread/resources/completions.py b/src/mixedbread/resources/chat.py similarity index 61% rename from src/mixedbread/resources/completions.py rename to src/mixedbread/resources/chat.py index 0132a58f..a5788f8f 100644 --- a/src/mixedbread/resources/completions.py +++ b/src/mixedbread/resources/chat.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 ( @@ -15,30 +15,30 @@ ) from .._base_client import make_request_options -__all__ = ["CompletionsResource", "AsyncCompletionsResource"] +__all__ = ["ChatResource", "AsyncChatResource"] -class CompletionsResource(SyncAPIResource): +class ChatResource(SyncAPIResource): @cached_property - def with_raw_response(self) -> CompletionsResourceWithRawResponse: + def with_raw_response(self) -> ChatResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ - return CompletionsResourceWithRawResponse(self) + return ChatResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> CompletionsResourceWithStreamingResponse: + def with_streaming_response(self) -> ChatResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ - return CompletionsResourceWithStreamingResponse(self) + return ChatResourceWithStreamingResponse(self) - def create( + def create_completion( self, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -46,7 +46,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, ) -> object: """ Create a chat completion using the provided parameters. @@ -73,27 +73,27 @@ def create( ) -class AsyncCompletionsResource(AsyncAPIResource): +class AsyncChatResource(AsyncAPIResource): @cached_property - def with_raw_response(self) -> AsyncCompletionsResourceWithRawResponse: + def with_raw_response(self) -> AsyncChatResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ - return AsyncCompletionsResourceWithRawResponse(self) + return AsyncChatResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> AsyncCompletionsResourceWithStreamingResponse: + def with_streaming_response(self) -> AsyncChatResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ - return AsyncCompletionsResourceWithStreamingResponse(self) + return AsyncChatResourceWithStreamingResponse(self) - async def create( + async def create_completion( self, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -101,7 +101,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, ) -> object: """ Create a chat completion using the provided parameters. @@ -128,37 +128,37 @@ async def create( ) -class CompletionsResourceWithRawResponse: - def __init__(self, completions: CompletionsResource) -> None: - self._completions = completions +class ChatResourceWithRawResponse: + def __init__(self, chat: ChatResource) -> None: + self._chat = chat - self.create = to_raw_response_wrapper( - completions.create, + self.create_completion = to_raw_response_wrapper( + chat.create_completion, ) -class AsyncCompletionsResourceWithRawResponse: - def __init__(self, completions: AsyncCompletionsResource) -> None: - self._completions = completions +class AsyncChatResourceWithRawResponse: + def __init__(self, chat: AsyncChatResource) -> None: + self._chat = chat - self.create = async_to_raw_response_wrapper( - completions.create, + self.create_completion = async_to_raw_response_wrapper( + chat.create_completion, ) -class CompletionsResourceWithStreamingResponse: - def __init__(self, completions: CompletionsResource) -> None: - self._completions = completions +class ChatResourceWithStreamingResponse: + def __init__(self, chat: ChatResource) -> None: + self._chat = chat - self.create = to_streamed_response_wrapper( - completions.create, + self.create_completion = to_streamed_response_wrapper( + chat.create_completion, ) -class AsyncCompletionsResourceWithStreamingResponse: - def __init__(self, completions: AsyncCompletionsResource) -> None: - self._completions = completions +class AsyncChatResourceWithStreamingResponse: + def __init__(self, chat: AsyncChatResource) -> None: + self._chat = chat - self.create = async_to_streamed_response_wrapper( - completions.create, + self.create_completion = async_to_streamed_response_wrapper( + chat.create_completion, ) diff --git a/src/mixedbread/resources/data_sources/__init__.py b/src/mixedbread/resources/data_sources/__init__.py new file mode 100644 index 00000000..6e04f6ec --- /dev/null +++ b/src/mixedbread/resources/data_sources/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .connectors import ( + ConnectorsResource, + AsyncConnectorsResource, + ConnectorsResourceWithRawResponse, + AsyncConnectorsResourceWithRawResponse, + ConnectorsResourceWithStreamingResponse, + AsyncConnectorsResourceWithStreamingResponse, +) +from .data_sources import ( + DataSourcesResource, + AsyncDataSourcesResource, + DataSourcesResourceWithRawResponse, + AsyncDataSourcesResourceWithRawResponse, + DataSourcesResourceWithStreamingResponse, + AsyncDataSourcesResourceWithStreamingResponse, +) + +__all__ = [ + "ConnectorsResource", + "AsyncConnectorsResource", + "ConnectorsResourceWithRawResponse", + "AsyncConnectorsResourceWithRawResponse", + "ConnectorsResourceWithStreamingResponse", + "AsyncConnectorsResourceWithStreamingResponse", + "DataSourcesResource", + "AsyncDataSourcesResource", + "DataSourcesResourceWithRawResponse", + "AsyncDataSourcesResourceWithRawResponse", + "DataSourcesResourceWithStreamingResponse", + "AsyncDataSourcesResourceWithStreamingResponse", +] diff --git a/src/mixedbread/resources/data_sources/connectors.py b/src/mixedbread/resources/data_sources/connectors.py new file mode 100644 index 00000000..bd913e76 --- /dev/null +++ b/src/mixedbread/resources/data_sources/connectors.py @@ -0,0 +1,749 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...pagination import SyncCursor, AsyncCursor +from ..._base_client import AsyncPaginator, make_request_options +from ...types.data_sources import connector_list_params, connector_create_params, connector_update_params +from ...types.data_sources.data_source_connector import DataSourceConnector +from ...types.data_sources.connector_delete_response import ConnectorDeleteResponse + +__all__ = ["ConnectorsResource", "AsyncConnectorsResource"] + + +class ConnectorsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ConnectorsResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return ConnectorsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ConnectorsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return ConnectorsResourceWithStreamingResponse(self) + + def create( + self, + data_source_id: str, + *, + store_id: str, + name: str | Omit = omit, + trigger_sync: bool | Omit = omit, + metadata: object | Omit = omit, + polling_interval: Union[int, str, 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, + ) -> DataSourceConnector: + """ + Create a new connector. + + Args: data_source_id: The ID of the data source to create a connector for. + params: The connector to create. + + Returns: The created connector. + + Args: + data_source_id: The ID of the data source to create a connector for + + store_id: The ID of the store + + name: The name of the connector + + trigger_sync: Whether the connector should be synced after creation + + metadata: The metadata of the connector + + polling_interval: Polling interval for the connector. Defaults to 30 minutes if not specified. Can + be provided as: + + - int: Number of seconds (e.g., 1800 for 30 minutes) + - str: Duration string (e.g., '30m', '1h', '2d') or ISO 8601 format (e.g., + 'PT30M', 'P1D') Valid range: 15 seconds to 30 days + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return self._post( + f"/v1/data_sources/{data_source_id}/connectors", + body=maybe_transform( + { + "store_id": store_id, + "name": name, + "trigger_sync": trigger_sync, + "metadata": metadata, + "polling_interval": polling_interval, + }, + connector_create_params.ConnectorCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSourceConnector, + ) + + def retrieve( + self, + connector_id: str, + *, + data_source_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, + ) -> DataSourceConnector: + """ + Get a connector by ID. + + Args: data_source_id: The ID of the data source to get a connector for. + connector_id: The ID of the connector to get. + + Returns: The connector. + + Args: + data_source_id: The ID of the data source to get a connector for + + connector_id: The ID of the connector to get + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + if not connector_id: + raise ValueError(f"Expected a non-empty value for `connector_id` but received {connector_id!r}") + return self._get( + f"/v1/data_sources/{data_source_id}/connectors/{connector_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSourceConnector, + ) + + def update( + self, + connector_id: str, + *, + data_source_id: str, + name: Optional[str] | Omit = omit, + metadata: Optional[Dict[str, object]] | Omit = omit, + trigger_sync: Optional[bool] | Omit = omit, + polling_interval: Union[int, str, 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, + ) -> DataSourceConnector: + """ + Update a connector. + + Args: data_source_id: The ID of the data source to update a connector for. + connector_id: The ID of the connector to update. params: The connector to + update. + + Returns: The updated connector. + + Args: + data_source_id: The ID of the data source to update a connector for + + connector_id: The ID of the connector to update + + name: The name of the connector + + metadata: The metadata of the connector + + trigger_sync: Whether the connector should be synced after update + + polling_interval: Polling interval for the connector. Defaults to 30 minutes if not specified. Can + be provided as: + + - int: Number of seconds (e.g., 1800 for 30 minutes) + - str: Duration string (e.g., '30m', '1h', '2d') or ISO 8601 format (e.g., + 'PT30M', 'P1D') Valid range: 15 seconds to 30 days + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + if not connector_id: + raise ValueError(f"Expected a non-empty value for `connector_id` but received {connector_id!r}") + return self._put( + f"/v1/data_sources/{data_source_id}/connectors/{connector_id}", + body=maybe_transform( + { + "name": name, + "metadata": metadata, + "trigger_sync": trigger_sync, + "polling_interval": polling_interval, + }, + connector_update_params.ConnectorUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSourceConnector, + ) + + def list( + self, + data_source_id: str, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: 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, + ) -> SyncCursor[DataSourceConnector]: + """ + Get all connectors for a data source. + + Args: data_source_id: The ID of the data source to get connectors for. + pagination: The pagination options. + + Returns: The list of connectors. + + Args: + data_source_id: The ID of the data source to get connectors for + + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return self._get_api_list( + f"/v1/data_sources/{data_source_id}/connectors", + page=SyncCursor[DataSourceConnector], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + }, + connector_list_params.ConnectorListParams, + ), + ), + model=DataSourceConnector, + ) + + def delete( + self, + connector_id: str, + *, + data_source_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, + ) -> ConnectorDeleteResponse: + """ + Delete a connector. + + Args: data_source_id: The ID of the data source to delete a connector for. + connector_id: The ID of the connector to delete. + + Returns: The deleted connector. + + Args: + data_source_id: The ID of the data source to delete a connector for + + connector_id: The ID of the connector to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + if not connector_id: + raise ValueError(f"Expected a non-empty value for `connector_id` but received {connector_id!r}") + return self._delete( + f"/v1/data_sources/{data_source_id}/connectors/{connector_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConnectorDeleteResponse, + ) + + +class AsyncConnectorsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncConnectorsResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return AsyncConnectorsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncConnectorsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return AsyncConnectorsResourceWithStreamingResponse(self) + + async def create( + self, + data_source_id: str, + *, + store_id: str, + name: str | Omit = omit, + trigger_sync: bool | Omit = omit, + metadata: object | Omit = omit, + polling_interval: Union[int, str, 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, + ) -> DataSourceConnector: + """ + Create a new connector. + + Args: data_source_id: The ID of the data source to create a connector for. + params: The connector to create. + + Returns: The created connector. + + Args: + data_source_id: The ID of the data source to create a connector for + + store_id: The ID of the store + + name: The name of the connector + + trigger_sync: Whether the connector should be synced after creation + + metadata: The metadata of the connector + + polling_interval: Polling interval for the connector. Defaults to 30 minutes if not specified. Can + be provided as: + + - int: Number of seconds (e.g., 1800 for 30 minutes) + - str: Duration string (e.g., '30m', '1h', '2d') or ISO 8601 format (e.g., + 'PT30M', 'P1D') Valid range: 15 seconds to 30 days + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return await self._post( + f"/v1/data_sources/{data_source_id}/connectors", + body=await async_maybe_transform( + { + "store_id": store_id, + "name": name, + "trigger_sync": trigger_sync, + "metadata": metadata, + "polling_interval": polling_interval, + }, + connector_create_params.ConnectorCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSourceConnector, + ) + + async def retrieve( + self, + connector_id: str, + *, + data_source_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, + ) -> DataSourceConnector: + """ + Get a connector by ID. + + Args: data_source_id: The ID of the data source to get a connector for. + connector_id: The ID of the connector to get. + + Returns: The connector. + + Args: + data_source_id: The ID of the data source to get a connector for + + connector_id: The ID of the connector to get + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + if not connector_id: + raise ValueError(f"Expected a non-empty value for `connector_id` but received {connector_id!r}") + return await self._get( + f"/v1/data_sources/{data_source_id}/connectors/{connector_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSourceConnector, + ) + + async def update( + self, + connector_id: str, + *, + data_source_id: str, + name: Optional[str] | Omit = omit, + metadata: Optional[Dict[str, object]] | Omit = omit, + trigger_sync: Optional[bool] | Omit = omit, + polling_interval: Union[int, str, 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, + ) -> DataSourceConnector: + """ + Update a connector. + + Args: data_source_id: The ID of the data source to update a connector for. + connector_id: The ID of the connector to update. params: The connector to + update. + + Returns: The updated connector. + + Args: + data_source_id: The ID of the data source to update a connector for + + connector_id: The ID of the connector to update + + name: The name of the connector + + metadata: The metadata of the connector + + trigger_sync: Whether the connector should be synced after update + + polling_interval: Polling interval for the connector. Defaults to 30 minutes if not specified. Can + be provided as: + + - int: Number of seconds (e.g., 1800 for 30 minutes) + - str: Duration string (e.g., '30m', '1h', '2d') or ISO 8601 format (e.g., + 'PT30M', 'P1D') Valid range: 15 seconds to 30 days + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + if not connector_id: + raise ValueError(f"Expected a non-empty value for `connector_id` but received {connector_id!r}") + return await self._put( + f"/v1/data_sources/{data_source_id}/connectors/{connector_id}", + body=await async_maybe_transform( + { + "name": name, + "metadata": metadata, + "trigger_sync": trigger_sync, + "polling_interval": polling_interval, + }, + connector_update_params.ConnectorUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSourceConnector, + ) + + def list( + self, + data_source_id: str, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: 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, + ) -> AsyncPaginator[DataSourceConnector, AsyncCursor[DataSourceConnector]]: + """ + Get all connectors for a data source. + + Args: data_source_id: The ID of the data source to get connectors for. + pagination: The pagination options. + + Returns: The list of connectors. + + Args: + data_source_id: The ID of the data source to get connectors for + + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return self._get_api_list( + f"/v1/data_sources/{data_source_id}/connectors", + page=AsyncCursor[DataSourceConnector], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + }, + connector_list_params.ConnectorListParams, + ), + ), + model=DataSourceConnector, + ) + + async def delete( + self, + connector_id: str, + *, + data_source_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, + ) -> ConnectorDeleteResponse: + """ + Delete a connector. + + Args: data_source_id: The ID of the data source to delete a connector for. + connector_id: The ID of the connector to delete. + + Returns: The deleted connector. + + Args: + data_source_id: The ID of the data source to delete a connector for + + connector_id: The ID of the connector to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + if not connector_id: + raise ValueError(f"Expected a non-empty value for `connector_id` but received {connector_id!r}") + return await self._delete( + f"/v1/data_sources/{data_source_id}/connectors/{connector_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConnectorDeleteResponse, + ) + + +class ConnectorsResourceWithRawResponse: + def __init__(self, connectors: ConnectorsResource) -> None: + self._connectors = connectors + + self.create = to_raw_response_wrapper( + connectors.create, + ) + self.retrieve = to_raw_response_wrapper( + connectors.retrieve, + ) + self.update = to_raw_response_wrapper( + connectors.update, + ) + self.list = to_raw_response_wrapper( + connectors.list, + ) + self.delete = to_raw_response_wrapper( + connectors.delete, + ) + + +class AsyncConnectorsResourceWithRawResponse: + def __init__(self, connectors: AsyncConnectorsResource) -> None: + self._connectors = connectors + + self.create = async_to_raw_response_wrapper( + connectors.create, + ) + self.retrieve = async_to_raw_response_wrapper( + connectors.retrieve, + ) + self.update = async_to_raw_response_wrapper( + connectors.update, + ) + self.list = async_to_raw_response_wrapper( + connectors.list, + ) + self.delete = async_to_raw_response_wrapper( + connectors.delete, + ) + + +class ConnectorsResourceWithStreamingResponse: + def __init__(self, connectors: ConnectorsResource) -> None: + self._connectors = connectors + + self.create = to_streamed_response_wrapper( + connectors.create, + ) + self.retrieve = to_streamed_response_wrapper( + connectors.retrieve, + ) + self.update = to_streamed_response_wrapper( + connectors.update, + ) + self.list = to_streamed_response_wrapper( + connectors.list, + ) + self.delete = to_streamed_response_wrapper( + connectors.delete, + ) + + +class AsyncConnectorsResourceWithStreamingResponse: + def __init__(self, connectors: AsyncConnectorsResource) -> None: + self._connectors = connectors + + self.create = async_to_streamed_response_wrapper( + connectors.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + connectors.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + connectors.update, + ) + self.list = async_to_streamed_response_wrapper( + connectors.list, + ) + self.delete = async_to_streamed_response_wrapper( + connectors.delete, + ) diff --git a/src/mixedbread/resources/data_sources/data_sources.py b/src/mixedbread/resources/data_sources/data_sources.py new file mode 100644 index 00000000..185eaedb --- /dev/null +++ b/src/mixedbread/resources/data_sources/data_sources.py @@ -0,0 +1,949 @@ +# 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 Oauth2Params, data_source_list_params, data_source_create_params, data_source_update_params +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import required_args, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from .connectors import ( + ConnectorsResource, + AsyncConnectorsResource, + ConnectorsResourceWithRawResponse, + AsyncConnectorsResourceWithRawResponse, + ConnectorsResourceWithStreamingResponse, + AsyncConnectorsResourceWithStreamingResponse, +) +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 SyncCursor, AsyncCursor +from ..._base_client import AsyncPaginator, make_request_options +from ...types.data_source import DataSource +from ...types.oauth2_params import Oauth2Params +from ...types.data_source_delete_response import DataSourceDeleteResponse + +__all__ = ["DataSourcesResource", "AsyncDataSourcesResource"] + + +class DataSourcesResource(SyncAPIResource): + @cached_property + def connectors(self) -> ConnectorsResource: + return ConnectorsResource(self._client) + + @cached_property + def with_raw_response(self) -> DataSourcesResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return DataSourcesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> DataSourcesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return DataSourcesResourceWithStreamingResponse(self) + + @overload + def create( + self, + *, + type: Literal["notion"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[data_source_create_params.NotionDataSourceAuthParams] | 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, + ) -> DataSource: + """ + Create a new data source. + + Args: params: The data source to create. + + Returns: The created data source. + + Args: + type: The type of data source to create + + name: The name of the data source + + metadata: The metadata of the data source + + auth_params: The authentication parameters of the data source. Notion supports OAuth2 and API + key. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 create( + self, + *, + type: Literal["linear"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[Oauth2Params] | 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, + ) -> DataSource: + """ + Create a new data source. + + Args: params: The data source to create. + + Returns: The created data source. + + Args: + type: The type of data source to create + + name: The name of the data source + + metadata: The metadata of the data source + + auth_params: Base class for OAuth2 create or update parameters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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(["name"]) + def create( + self, + *, + type: Literal["notion"] | Literal["linear"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[data_source_create_params.NotionDataSourceAuthParams] + | Optional[Oauth2Params] + | 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, + ) -> DataSource: + return self._post( + "/v1/data_sources/", + body=maybe_transform( + { + "type": type, + "name": name, + "metadata": metadata, + "auth_params": auth_params, + }, + data_source_create_params.DataSourceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSource, + ) + + def retrieve( + self, + data_source_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, + ) -> DataSource: + """ + Get a data source by ID. + + Args: data_source_id: The ID of the data source to fetch. + + Returns: The data source. + + Args: + data_source_id: The ID of the data source to fetch + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return self._get( + f"/v1/data_sources/{data_source_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSource, + ) + + @overload + def update( + self, + data_source_id: str, + *, + type: Literal["notion"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[data_source_update_params.NotionDataSourceAuthParams] | 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, + ) -> DataSource: + """Update a data source. + + Args: data_source_id: The ID of the data source to update. + + params: The data + source to update. + + Returns: The updated data source. + + Args: + data_source_id: The ID of the data source to update + + type: The type of data source to create + + name: The name of the data source + + metadata: The metadata of the data source + + auth_params: The authentication parameters of the data source. Notion supports OAuth2 and API + key. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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, + data_source_id: str, + *, + type: Literal["linear"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[Oauth2Params] | 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, + ) -> DataSource: + """Update a data source. + + Args: data_source_id: The ID of the data source to update. + + params: The data + source to update. + + Returns: The updated data source. + + Args: + data_source_id: The ID of the data source to update + + type: The type of data source to create + + name: The name of the data source + + metadata: The metadata of the data source + + auth_params: Base class for OAuth2 create or update parameters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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(["name"]) + def update( + self, + data_source_id: str, + *, + type: Literal["notion"] | Literal["linear"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[data_source_update_params.NotionDataSourceAuthParams] + | Optional[Oauth2Params] + | 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, + ) -> DataSource: + if not data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return self._put( + f"/v1/data_sources/{data_source_id}", + body=maybe_transform( + { + "type": type, + "name": name, + "metadata": metadata, + "auth_params": auth_params, + }, + data_source_update_params.DataSourceUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSource, + ) + + def list( + self, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: 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, + ) -> SyncCursor[DataSource]: + """ + Get all data sources. + + Returns: The list of data sources. + + Args: + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive 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 + """ + return self._get_api_list( + "/v1/data_sources/", + page=SyncCursor[DataSource], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + }, + data_source_list_params.DataSourceListParams, + ), + ), + model=DataSource, + ) + + def delete( + self, + data_source_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, + ) -> DataSourceDeleteResponse: + """ + Delete a data source. + + Args: data_source_id: The ID of the data source to delete. + + Args: + data_source_id: The ID of the data source to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return self._delete( + f"/v1/data_sources/{data_source_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSourceDeleteResponse, + ) + + +class AsyncDataSourcesResource(AsyncAPIResource): + @cached_property + def connectors(self) -> AsyncConnectorsResource: + return AsyncConnectorsResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncDataSourcesResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return AsyncDataSourcesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncDataSourcesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return AsyncDataSourcesResourceWithStreamingResponse(self) + + @overload + async def create( + self, + *, + type: Literal["notion"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[data_source_create_params.NotionDataSourceAuthParams] | 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, + ) -> DataSource: + """ + Create a new data source. + + Args: params: The data source to create. + + Returns: The created data source. + + Args: + type: The type of data source to create + + name: The name of the data source + + metadata: The metadata of the data source + + auth_params: The authentication parameters of the data source. Notion supports OAuth2 and API + key. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 create( + self, + *, + type: Literal["linear"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[Oauth2Params] | 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, + ) -> DataSource: + """ + Create a new data source. + + Args: params: The data source to create. + + Returns: The created data source. + + Args: + type: The type of data source to create + + name: The name of the data source + + metadata: The metadata of the data source + + auth_params: Base class for OAuth2 create or update parameters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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(["name"]) + async def create( + self, + *, + type: Literal["notion"] | Literal["linear"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[data_source_create_params.NotionDataSourceAuthParams] + | Optional[Oauth2Params] + | 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, + ) -> DataSource: + return await self._post( + "/v1/data_sources/", + body=await async_maybe_transform( + { + "type": type, + "name": name, + "metadata": metadata, + "auth_params": auth_params, + }, + data_source_create_params.DataSourceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSource, + ) + + async def retrieve( + self, + data_source_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, + ) -> DataSource: + """ + Get a data source by ID. + + Args: data_source_id: The ID of the data source to fetch. + + Returns: The data source. + + Args: + data_source_id: The ID of the data source to fetch + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return await self._get( + f"/v1/data_sources/{data_source_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSource, + ) + + @overload + async def update( + self, + data_source_id: str, + *, + type: Literal["notion"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[data_source_update_params.NotionDataSourceAuthParams] | 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, + ) -> DataSource: + """Update a data source. + + Args: data_source_id: The ID of the data source to update. + + params: The data + source to update. + + Returns: The updated data source. + + Args: + data_source_id: The ID of the data source to update + + type: The type of data source to create + + name: The name of the data source + + metadata: The metadata of the data source + + auth_params: The authentication parameters of the data source. Notion supports OAuth2 and API + key. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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, + data_source_id: str, + *, + type: Literal["linear"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[Oauth2Params] | 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, + ) -> DataSource: + """Update a data source. + + Args: data_source_id: The ID of the data source to update. + + params: The data + source to update. + + Returns: The updated data source. + + Args: + data_source_id: The ID of the data source to update + + type: The type of data source to create + + name: The name of the data source + + metadata: The metadata of the data source + + auth_params: Base class for OAuth2 create or update parameters. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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(["name"]) + async def update( + self, + data_source_id: str, + *, + type: Literal["notion"] | Literal["linear"] | Omit = omit, + name: str, + metadata: object | Omit = omit, + auth_params: Optional[data_source_update_params.NotionDataSourceAuthParams] + | Optional[Oauth2Params] + | 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, + ) -> DataSource: + if not data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return await self._put( + f"/v1/data_sources/{data_source_id}", + body=await async_maybe_transform( + { + "type": type, + "name": name, + "metadata": metadata, + "auth_params": auth_params, + }, + data_source_update_params.DataSourceUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSource, + ) + + def list( + self, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: 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, + ) -> AsyncPaginator[DataSource, AsyncCursor[DataSource]]: + """ + Get all data sources. + + Returns: The list of data sources. + + Args: + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive 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 + """ + return self._get_api_list( + "/v1/data_sources/", + page=AsyncCursor[DataSource], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + }, + data_source_list_params.DataSourceListParams, + ), + ), + model=DataSource, + ) + + async def delete( + self, + data_source_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, + ) -> DataSourceDeleteResponse: + """ + Delete a data source. + + Args: data_source_id: The ID of the data source to delete. + + Args: + data_source_id: The ID of the data source to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 data_source_id: + raise ValueError(f"Expected a non-empty value for `data_source_id` but received {data_source_id!r}") + return await self._delete( + f"/v1/data_sources/{data_source_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DataSourceDeleteResponse, + ) + + +class DataSourcesResourceWithRawResponse: + def __init__(self, data_sources: DataSourcesResource) -> None: + self._data_sources = data_sources + + self.create = to_raw_response_wrapper( + data_sources.create, + ) + self.retrieve = to_raw_response_wrapper( + data_sources.retrieve, + ) + self.update = to_raw_response_wrapper( + data_sources.update, + ) + self.list = to_raw_response_wrapper( + data_sources.list, + ) + self.delete = to_raw_response_wrapper( + data_sources.delete, + ) + + @cached_property + def connectors(self) -> ConnectorsResourceWithRawResponse: + return ConnectorsResourceWithRawResponse(self._data_sources.connectors) + + +class AsyncDataSourcesResourceWithRawResponse: + def __init__(self, data_sources: AsyncDataSourcesResource) -> None: + self._data_sources = data_sources + + self.create = async_to_raw_response_wrapper( + data_sources.create, + ) + self.retrieve = async_to_raw_response_wrapper( + data_sources.retrieve, + ) + self.update = async_to_raw_response_wrapper( + data_sources.update, + ) + self.list = async_to_raw_response_wrapper( + data_sources.list, + ) + self.delete = async_to_raw_response_wrapper( + data_sources.delete, + ) + + @cached_property + def connectors(self) -> AsyncConnectorsResourceWithRawResponse: + return AsyncConnectorsResourceWithRawResponse(self._data_sources.connectors) + + +class DataSourcesResourceWithStreamingResponse: + def __init__(self, data_sources: DataSourcesResource) -> None: + self._data_sources = data_sources + + self.create = to_streamed_response_wrapper( + data_sources.create, + ) + self.retrieve = to_streamed_response_wrapper( + data_sources.retrieve, + ) + self.update = to_streamed_response_wrapper( + data_sources.update, + ) + self.list = to_streamed_response_wrapper( + data_sources.list, + ) + self.delete = to_streamed_response_wrapper( + data_sources.delete, + ) + + @cached_property + def connectors(self) -> ConnectorsResourceWithStreamingResponse: + return ConnectorsResourceWithStreamingResponse(self._data_sources.connectors) + + +class AsyncDataSourcesResourceWithStreamingResponse: + def __init__(self, data_sources: AsyncDataSourcesResource) -> None: + self._data_sources = data_sources + + self.create = async_to_streamed_response_wrapper( + data_sources.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + data_sources.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + data_sources.update, + ) + self.list = async_to_streamed_response_wrapper( + data_sources.list, + ) + self.delete = async_to_streamed_response_wrapper( + data_sources.delete, + ) + + @cached_property + def connectors(self) -> AsyncConnectorsResourceWithStreamingResponse: + return AsyncConnectorsResourceWithStreamingResponse(self._data_sources.connectors) diff --git a/src/mixedbread/resources/embeddings.py b/src/mixedbread/resources/embeddings.py index fb15b5c6..d2c74634 100644 --- a/src/mixedbread/resources/embeddings.py +++ b/src/mixedbread/resources/embeddings.py @@ -3,16 +3,12 @@ from __future__ import annotations from typing import List, Union, Optional -from typing_extensions import Literal import httpx from ..types import embedding_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -22,6 +18,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.encoding_format import EncodingFormat from ..types.embedding_create_response import EmbeddingCreateResponse __all__ = ["EmbeddingsResource", "AsyncEmbeddingsResource"] @@ -31,10 +28,10 @@ class EmbeddingsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> EmbeddingsResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return EmbeddingsResourceWithRawResponse(self) @@ -43,29 +40,25 @@ def with_streaming_response(self) -> EmbeddingsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return EmbeddingsResourceWithStreamingResponse(self) def create( self, *, - input: embedding_create_params.Input, model: str, - dimensions: Optional[int] | NotGiven = NOT_GIVEN, - encoding_format: Union[ - Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"], - List[Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"]], - ] - | NotGiven = NOT_GIVEN, - normalized: bool | NotGiven = NOT_GIVEN, - prompt: Optional[str] | NotGiven = NOT_GIVEN, + input: Union[str, SequenceNotStr[str]], + dimensions: Optional[int] | Omit = omit, + prompt: Optional[str] | Omit = omit, + normalized: bool | Omit = omit, + encoding_format: Union[EncodingFormat, List[EncodingFormat]] | 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, ) -> EmbeddingCreateResponse: """ Create embeddings for text or images using the specified model, encoding format, @@ -76,17 +69,18 @@ def create( Returns: EmbeddingCreateResponse: The response containing the embeddings. Args: - input: The input to create embeddings for. - model: The model to use for creating embeddings. + input: The input to create embeddings for. + dimensions: The number of dimensions to use for the embeddings. - encoding_format: The encoding format of the embeddings. + prompt: The prompt to use for the embedding creation. normalized: Whether to normalize the embeddings. - prompt: The prompt to use for the embedding creation. + encoding_format: The encoding format(s) of the embeddings. Can be a single format or a list of + formats. extra_headers: Send extra headers @@ -100,12 +94,12 @@ def create( "/v1/embeddings", body=maybe_transform( { - "input": input, "model": model, + "input": input, "dimensions": dimensions, - "encoding_format": encoding_format, - "normalized": normalized, "prompt": prompt, + "normalized": normalized, + "encoding_format": encoding_format, }, embedding_create_params.EmbeddingCreateParams, ), @@ -120,10 +114,10 @@ class AsyncEmbeddingsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncEmbeddingsResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return AsyncEmbeddingsResourceWithRawResponse(self) @@ -132,29 +126,25 @@ def with_streaming_response(self) -> AsyncEmbeddingsResourceWithStreamingRespons """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return AsyncEmbeddingsResourceWithStreamingResponse(self) async def create( self, *, - input: embedding_create_params.Input, model: str, - dimensions: Optional[int] | NotGiven = NOT_GIVEN, - encoding_format: Union[ - Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"], - List[Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"]], - ] - | NotGiven = NOT_GIVEN, - normalized: bool | NotGiven = NOT_GIVEN, - prompt: Optional[str] | NotGiven = NOT_GIVEN, + input: Union[str, SequenceNotStr[str]], + dimensions: Optional[int] | Omit = omit, + prompt: Optional[str] | Omit = omit, + normalized: bool | Omit = omit, + encoding_format: Union[EncodingFormat, List[EncodingFormat]] | 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, ) -> EmbeddingCreateResponse: """ Create embeddings for text or images using the specified model, encoding format, @@ -165,17 +155,18 @@ async def create( Returns: EmbeddingCreateResponse: The response containing the embeddings. Args: - input: The input to create embeddings for. - model: The model to use for creating embeddings. + input: The input to create embeddings for. + dimensions: The number of dimensions to use for the embeddings. - encoding_format: The encoding format of the embeddings. + prompt: The prompt to use for the embedding creation. normalized: Whether to normalize the embeddings. - prompt: The prompt to use for the embedding creation. + encoding_format: The encoding format(s) of the embeddings. Can be a single format or a list of + formats. extra_headers: Send extra headers @@ -189,12 +180,12 @@ async def create( "/v1/embeddings", body=await async_maybe_transform( { - "input": input, "model": model, + "input": input, "dimensions": dimensions, - "encoding_format": encoding_format, - "normalized": normalized, "prompt": prompt, + "normalized": normalized, + "encoding_format": encoding_format, }, embedding_create_params.EmbeddingCreateParams, ), diff --git a/src/mixedbread/resources/extractions/content.py b/src/mixedbread/resources/extractions/content.py index 9cefef45..b2111bb3 100644 --- a/src/mixedbread/resources/extractions/content.py +++ b/src/mixedbread/resources/extractions/content.py @@ -2,13 +2,12 @@ from __future__ import annotations +from typing import Dict, Union, Iterable, Optional + import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -28,10 +27,10 @@ class ContentResource(SyncAPIResource): @cached_property def with_raw_response(self) -> ContentResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return ContentResourceWithRawResponse(self) @@ -40,21 +39,22 @@ def with_streaming_response(self) -> ContentResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return ContentResourceWithStreamingResponse(self) def create( self, *, - content: str, - json_schema: object, + content: Union[str, SequenceNotStr[str], Iterable[content_create_params.ContentUnionMember2]], + json_schema: Dict[str, object], + instructions: 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, ) -> ExtractionResult: """ Extract content from a string using the provided schema. @@ -68,6 +68,8 @@ def create( json_schema: The JSON schema to use for extraction + instructions: Additional instructions for the extraction + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -82,6 +84,7 @@ def create( { "content": content, "json_schema": json_schema, + "instructions": instructions, }, content_create_params.ContentCreateParams, ), @@ -96,10 +99,10 @@ class AsyncContentResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncContentResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return AsyncContentResourceWithRawResponse(self) @@ -108,21 +111,22 @@ def with_streaming_response(self) -> AsyncContentResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return AsyncContentResourceWithStreamingResponse(self) async def create( self, *, - content: str, - json_schema: object, + content: Union[str, SequenceNotStr[str], Iterable[content_create_params.ContentUnionMember2]], + json_schema: Dict[str, object], + instructions: 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, ) -> ExtractionResult: """ Extract content from a string using the provided schema. @@ -136,6 +140,8 @@ async def create( json_schema: The JSON schema to use for extraction + instructions: Additional instructions for the extraction + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -150,6 +156,7 @@ async def create( { "content": content, "json_schema": json_schema, + "instructions": instructions, }, content_create_params.ContentCreateParams, ), diff --git a/src/mixedbread/resources/extractions/extractions.py b/src/mixedbread/resources/extractions/extractions.py index 1b6da3b1..eebb3e40 100644 --- a/src/mixedbread/resources/extractions/extractions.py +++ b/src/mixedbread/resources/extractions/extractions.py @@ -48,10 +48,10 @@ def content(self) -> ContentResource: @cached_property def with_raw_response(self) -> ExtractionsResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return ExtractionsResourceWithRawResponse(self) @@ -60,7 +60,7 @@ def with_streaming_response(self) -> ExtractionsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return ExtractionsResourceWithStreamingResponse(self) @@ -81,10 +81,10 @@ def content(self) -> AsyncContentResource: @cached_property def with_raw_response(self) -> AsyncExtractionsResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return AsyncExtractionsResourceWithRawResponse(self) @@ -93,7 +93,7 @@ def with_streaming_response(self) -> AsyncExtractionsResourceWithStreamingRespon """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return AsyncExtractionsResourceWithStreamingResponse(self) diff --git a/src/mixedbread/resources/extractions/jobs.py b/src/mixedbread/resources/extractions/jobs.py index 8ac9016f..ae48bbf3 100644 --- a/src/mixedbread/resources/extractions/jobs.py +++ b/src/mixedbread/resources/extractions/jobs.py @@ -2,13 +2,12 @@ from __future__ import annotations +from typing import Dict + import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -28,10 +27,10 @@ class JobsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> JobsResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return JobsResourceWithRawResponse(self) @@ -40,7 +39,7 @@ def with_streaming_response(self) -> JobsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return JobsResourceWithStreamingResponse(self) @@ -48,13 +47,13 @@ def create( self, *, file_id: str, - json_schema: object, + json_schema: Dict[str, 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExtractionJob: """ Start an extraction job for the provided file and schema. @@ -100,7 +99,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, ) -> ExtractionJob: """ Get detailed information about a specific extraction job. @@ -135,10 +134,10 @@ class AsyncJobsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncJobsResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return AsyncJobsResourceWithRawResponse(self) @@ -147,7 +146,7 @@ def with_streaming_response(self) -> AsyncJobsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return AsyncJobsResourceWithStreamingResponse(self) @@ -155,13 +154,13 @@ async def create( self, *, file_id: str, - json_schema: object, + json_schema: Dict[str, 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExtractionJob: """ Start an extraction job for the provided file and schema. @@ -207,7 +206,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, ) -> ExtractionJob: """ Get detailed information about a specific extraction job. diff --git a/src/mixedbread/resources/extractions/schema.py b/src/mixedbread/resources/extractions/schema.py index c0e507e0..6eabe54e 100644 --- a/src/mixedbread/resources/extractions/schema.py +++ b/src/mixedbread/resources/extractions/schema.py @@ -2,13 +2,12 @@ from __future__ import annotations +from typing import Dict + import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -30,10 +29,10 @@ class SchemaResource(SyncAPIResource): @cached_property def with_raw_response(self) -> SchemaResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return SchemaResourceWithRawResponse(self) @@ -42,7 +41,7 @@ def with_streaming_response(self) -> SchemaResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return SchemaResourceWithStreamingResponse(self) @@ -55,7 +54,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, ) -> CreatedJsonSchema: """ Create a schema with the provided parameters. @@ -87,13 +86,13 @@ def create( def enhance( self, *, - json_schema: object, + json_schema: Dict[str, 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EnhancedJsonSchema: """ Enhance a schema by enriching the descriptions to aid extraction. @@ -125,13 +124,13 @@ def enhance( def validate( self, *, - json_schema: object, + json_schema: Dict[str, 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ValidatedJsonSchema: """ Validate a schema. @@ -165,10 +164,10 @@ class AsyncSchemaResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncSchemaResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return AsyncSchemaResourceWithRawResponse(self) @@ -177,7 +176,7 @@ def with_streaming_response(self) -> AsyncSchemaResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return AsyncSchemaResourceWithStreamingResponse(self) @@ -190,7 +189,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, ) -> CreatedJsonSchema: """ Create a schema with the provided parameters. @@ -222,13 +221,13 @@ async def create( async def enhance( self, *, - json_schema: object, + json_schema: Dict[str, 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EnhancedJsonSchema: """ Enhance a schema by enriching the descriptions to aid extraction. @@ -260,13 +259,13 @@ async def enhance( async def validate( self, *, - json_schema: object, + json_schema: Dict[str, 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ValidatedJsonSchema: """ Validate a schema. diff --git a/src/mixedbread/resources/files/__init__.py b/src/mixedbread/resources/files/__init__.py index d1f68da6..13fe2123 100644 --- a/src/mixedbread/resources/files/__init__.py +++ b/src/mixedbread/resources/files/__init__.py @@ -8,22 +8,22 @@ FilesResourceWithStreamingResponse, AsyncFilesResourceWithStreamingResponse, ) -from .content import ( - ContentResource, - AsyncContentResource, - ContentResourceWithRawResponse, - AsyncContentResourceWithRawResponse, - ContentResourceWithStreamingResponse, - AsyncContentResourceWithStreamingResponse, +from .uploads import ( + UploadsResource, + AsyncUploadsResource, + UploadsResourceWithRawResponse, + AsyncUploadsResourceWithRawResponse, + UploadsResourceWithStreamingResponse, + AsyncUploadsResourceWithStreamingResponse, ) __all__ = [ - "ContentResource", - "AsyncContentResource", - "ContentResourceWithRawResponse", - "AsyncContentResourceWithRawResponse", - "ContentResourceWithStreamingResponse", - "AsyncContentResourceWithStreamingResponse", + "UploadsResource", + "AsyncUploadsResource", + "UploadsResourceWithRawResponse", + "AsyncUploadsResourceWithRawResponse", + "UploadsResourceWithStreamingResponse", + "AsyncUploadsResourceWithStreamingResponse", "FilesResource", "AsyncFilesResource", "FilesResourceWithRawResponse", diff --git a/src/mixedbread/resources/files/content.py b/src/mixedbread/resources/files/content.py deleted file mode 100644 index a55cb4c1..00000000 --- a/src/mixedbread/resources/files/content.py +++ /dev/null @@ -1,184 +0,0 @@ -# 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 ( - BinaryAPIResponse, - AsyncBinaryAPIResponse, - StreamedBinaryAPIResponse, - AsyncStreamedBinaryAPIResponse, - to_custom_raw_response_wrapper, - to_custom_streamed_response_wrapper, - async_to_custom_raw_response_wrapper, - async_to_custom_streamed_response_wrapper, -) -from ..._base_client import make_request_options - -__all__ = ["ContentResource", "AsyncContentResource"] - - -class ContentResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> ContentResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return ContentResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ContentResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return ContentResourceWithStreamingResponse(self) - - def retrieve( - self, - file_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, - ) -> BinaryAPIResponse: - """ - Download a specific file by its ID. - - Args: file_id: The ID of the file to download. - - Returns: FileStreamResponse: The response containing the file to be downloaded. - - Args: - file_id: The ID of the file to download - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 file_id: - raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") - extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} - return self._get( - f"/v1/files/{file_id}/content", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=BinaryAPIResponse, - ) - - -class AsyncContentResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncContentResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return AsyncContentResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncContentResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return AsyncContentResourceWithStreamingResponse(self) - - async def retrieve( - self, - file_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, - ) -> AsyncBinaryAPIResponse: - """ - Download a specific file by its ID. - - Args: file_id: The ID of the file to download. - - Returns: FileStreamResponse: The response containing the file to be downloaded. - - Args: - file_id: The ID of the file to download - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 file_id: - raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") - extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} - return await self._get( - f"/v1/files/{file_id}/content", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AsyncBinaryAPIResponse, - ) - - -class ContentResourceWithRawResponse: - def __init__(self, content: ContentResource) -> None: - self._content = content - - self.retrieve = to_custom_raw_response_wrapper( - content.retrieve, - BinaryAPIResponse, - ) - - -class AsyncContentResourceWithRawResponse: - def __init__(self, content: AsyncContentResource) -> None: - self._content = content - - self.retrieve = async_to_custom_raw_response_wrapper( - content.retrieve, - AsyncBinaryAPIResponse, - ) - - -class ContentResourceWithStreamingResponse: - def __init__(self, content: ContentResource) -> None: - self._content = content - - self.retrieve = to_custom_streamed_response_wrapper( - content.retrieve, - StreamedBinaryAPIResponse, - ) - - -class AsyncContentResourceWithStreamingResponse: - def __init__(self, content: AsyncContentResource) -> None: - self._content = content - - self.retrieve = async_to_custom_streamed_response_wrapper( - content.retrieve, - AsyncStreamedBinaryAPIResponse, - ) diff --git a/src/mixedbread/resources/files/files.py b/src/mixedbread/resources/files/files.py index 1ec24376..7aec6bff 100644 --- a/src/mixedbread/resources/files/files.py +++ b/src/mixedbread/resources/files/files.py @@ -2,54 +2,57 @@ from __future__ import annotations -from typing import Mapping, cast +from typing import Mapping, Optional, cast import httpx from ...types import file_list_params, file_create_params, file_update_params -from .content import ( - ContentResource, - AsyncContentResource, - ContentResourceWithRawResponse, - AsyncContentResourceWithRawResponse, - ContentResourceWithStreamingResponse, - AsyncContentResourceWithStreamingResponse, -) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from ..._utils import ( - extract_files, - maybe_transform, - deepcopy_minimal, - async_maybe_transform, +from .uploads import ( + UploadsResource, + AsyncUploadsResource, + UploadsResourceWithRawResponse, + AsyncUploadsResourceWithRawResponse, + UploadsResourceWithStreamingResponse, + AsyncUploadsResourceWithStreamingResponse, ) +from ..._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given +from ..._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( + BinaryAPIResponse, + AsyncBinaryAPIResponse, + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, + to_custom_raw_response_wrapper, async_to_streamed_response_wrapper, + to_custom_streamed_response_wrapper, + async_to_custom_raw_response_wrapper, + async_to_custom_streamed_response_wrapper, ) -from ..._base_client import make_request_options +from ...pagination import SyncCursor, AsyncCursor +from ..._base_client import AsyncPaginator, make_request_options from ...types.file_object import FileObject -from ...types.file_deleted import FileDeleted -from ...types.file_list_response import FileListResponse +from ...types.file_delete_response import FileDeleteResponse __all__ = ["FilesResource", "AsyncFilesResource"] class FilesResource(SyncAPIResource): @cached_property - def content(self) -> ContentResource: - return ContentResource(self._client) + def uploads(self) -> UploadsResource: + return UploadsResource(self._client) @cached_property def with_raw_response(self) -> FilesResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return FilesResourceWithRawResponse(self) @@ -58,7 +61,7 @@ def with_streaming_response(self) -> FilesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return FilesResourceWithStreamingResponse(self) @@ -71,7 +74,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, ) -> FileObject: """ Upload a new file. @@ -116,7 +119,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, ) -> FileObject: """ Retrieve details of a specific file by its ID. @@ -156,7 +159,7 @@ def update( 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, ) -> FileObject: """ Update the details of a specific file. @@ -199,15 +202,18 @@ def update( def list( self, *, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + q: 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, - ) -> FileListResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursor[FileObject]: """ List all files for the authenticated user. @@ -216,9 +222,17 @@ def list( Returns: A list of files belonging to the user. Args: - limit: Maximum number of items to return per page + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. - offset: Offset of the first item to return + include_total: Whether to include total count in response (expensive operation) + + q: Search query for fuzzy matching over name and description fields extra_headers: Send extra headers @@ -228,8 +242,9 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get( + return self._get_api_list( "/v1/files", + page=SyncCursor[FileObject], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -238,12 +253,15 @@ def list( query=maybe_transform( { "limit": limit, - "offset": offset, + "after": after, + "before": before, + "include_total": include_total, + "q": q, }, file_list_params.FileListParams, ), ), - cast_to=FileListResponse, + model=FileObject, ) def delete( @@ -255,8 +273,8 @@ 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, - ) -> FileDeleted: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileDeleteResponse: """ Delete a specific file by its ID. @@ -282,22 +300,62 @@ def delete( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=FileDeleted, + cast_to=FileDeleteResponse, + ) + + def content( + self, + file_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, + ) -> BinaryAPIResponse: + """ + Download a specific file by its ID. + + Args: file_id: The ID of the file to download. + + Returns: FileStreamResponse: The response containing the file to be downloaded. + + Args: + file_id: The ID of the file to download + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} + return self._get( + f"/v1/files/{file_id}/content", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BinaryAPIResponse, ) class AsyncFilesResource(AsyncAPIResource): @cached_property - def content(self) -> AsyncContentResource: - return AsyncContentResource(self._client) + def uploads(self) -> AsyncUploadsResource: + return AsyncUploadsResource(self._client) @cached_property def with_raw_response(self) -> AsyncFilesResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return AsyncFilesResourceWithRawResponse(self) @@ -306,7 +364,7 @@ def with_streaming_response(self) -> AsyncFilesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return AsyncFilesResourceWithStreamingResponse(self) @@ -319,7 +377,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, ) -> FileObject: """ Upload a new file. @@ -364,7 +422,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, ) -> FileObject: """ Retrieve details of a specific file by its ID. @@ -404,7 +462,7 @@ async def update( 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, ) -> FileObject: """ Update the details of a specific file. @@ -444,18 +502,21 @@ async def update( cast_to=FileObject, ) - async def list( + def list( self, *, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + q: 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, - ) -> FileListResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[FileObject, AsyncCursor[FileObject]]: """ List all files for the authenticated user. @@ -464,9 +525,17 @@ async def list( Returns: A list of files belonging to the user. Args: - limit: Maximum number of items to return per page + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. - offset: Offset of the first item to return + include_total: Whether to include total count in response (expensive operation) + + q: Search query for fuzzy matching over name and description fields extra_headers: Send extra headers @@ -476,22 +545,26 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._get( + return self._get_api_list( "/v1/files", + page=AsyncCursor[FileObject], 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( { "limit": limit, - "offset": offset, + "after": after, + "before": before, + "include_total": include_total, + "q": q, }, file_list_params.FileListParams, ), ), - cast_to=FileListResponse, + model=FileObject, ) async def delete( @@ -503,8 +576,8 @@ 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, - ) -> FileDeleted: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileDeleteResponse: """ Delete a specific file by its ID. @@ -530,7 +603,47 @@ async def delete( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=FileDeleted, + cast_to=FileDeleteResponse, + ) + + async def content( + self, + file_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, + ) -> AsyncBinaryAPIResponse: + """ + Download a specific file by its ID. + + Args: file_id: The ID of the file to download. + + Returns: FileStreamResponse: The response containing the file to be downloaded. + + Args: + file_id: The ID of the file to download + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} + return await self._get( + f"/v1/files/{file_id}/content", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AsyncBinaryAPIResponse, ) @@ -553,10 +666,14 @@ def __init__(self, files: FilesResource) -> None: self.delete = to_raw_response_wrapper( files.delete, ) + self.content = to_custom_raw_response_wrapper( + files.content, + BinaryAPIResponse, + ) @cached_property - def content(self) -> ContentResourceWithRawResponse: - return ContentResourceWithRawResponse(self._files.content) + def uploads(self) -> UploadsResourceWithRawResponse: + return UploadsResourceWithRawResponse(self._files.uploads) class AsyncFilesResourceWithRawResponse: @@ -578,10 +695,14 @@ def __init__(self, files: AsyncFilesResource) -> None: self.delete = async_to_raw_response_wrapper( files.delete, ) + self.content = async_to_custom_raw_response_wrapper( + files.content, + AsyncBinaryAPIResponse, + ) @cached_property - def content(self) -> AsyncContentResourceWithRawResponse: - return AsyncContentResourceWithRawResponse(self._files.content) + def uploads(self) -> AsyncUploadsResourceWithRawResponse: + return AsyncUploadsResourceWithRawResponse(self._files.uploads) class FilesResourceWithStreamingResponse: @@ -603,10 +724,14 @@ def __init__(self, files: FilesResource) -> None: self.delete = to_streamed_response_wrapper( files.delete, ) + self.content = to_custom_streamed_response_wrapper( + files.content, + StreamedBinaryAPIResponse, + ) @cached_property - def content(self) -> ContentResourceWithStreamingResponse: - return ContentResourceWithStreamingResponse(self._files.content) + def uploads(self) -> UploadsResourceWithStreamingResponse: + return UploadsResourceWithStreamingResponse(self._files.uploads) class AsyncFilesResourceWithStreamingResponse: @@ -628,7 +753,11 @@ def __init__(self, files: AsyncFilesResource) -> None: self.delete = async_to_streamed_response_wrapper( files.delete, ) + self.content = async_to_custom_streamed_response_wrapper( + files.content, + AsyncStreamedBinaryAPIResponse, + ) @cached_property - def content(self) -> AsyncContentResourceWithStreamingResponse: - return AsyncContentResourceWithStreamingResponse(self._files.content) + def uploads(self) -> AsyncUploadsResourceWithStreamingResponse: + return AsyncUploadsResourceWithStreamingResponse(self._files.uploads) diff --git a/src/mixedbread/resources/files/uploads.py b/src/mixedbread/resources/files/uploads.py new file mode 100644 index 00000000..dc410621 --- /dev/null +++ b/src/mixedbread/resources/files/uploads.py @@ -0,0 +1,520 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...types.files import upload_create_params, upload_complete_params +from ..._base_client import make_request_options +from ...types.file_object import FileObject +from ...types.files.upload_list_response import UploadListResponse +from ...types.files.upload_abort_response import UploadAbortResponse +from ...types.files.upload_create_response import UploadCreateResponse +from ...types.files.upload_retrieve_response import UploadRetrieveResponse +from ...types.files.multipart_upload_part_param import MultipartUploadPartParam + +__all__ = ["UploadsResource", "AsyncUploadsResource"] + + +class UploadsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> UploadsResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return UploadsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UploadsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return UploadsResourceWithStreamingResponse(self) + + def create( + self, + *, + filename: str, + file_size: int, + mime_type: str, + part_count: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UploadCreateResponse: + """ + Initiate a multipart upload and receive presigned URLs for uploading parts + directly to storage. + + Args: + filename: Name of the file including extension + + file_size: Total size of the file in bytes + + mime_type: MIME type of the file + + part_count: Number of parts to split the upload into + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/files/uploads", + body=maybe_transform( + { + "filename": filename, + "file_size": file_size, + "mime_type": mime_type, + "part_count": part_count, + }, + upload_create_params.UploadCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UploadCreateResponse, + ) + + def retrieve( + self, + upload_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, + ) -> UploadRetrieveResponse: + """ + Get a multipart upload's details with fresh presigned URLs for any parts not yet + uploaded. + + Args: + upload_id: The ID of the multipart upload + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 upload_id: + raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") + return self._get( + f"/v1/files/uploads/{upload_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UploadRetrieveResponse, + ) + + 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, + ) -> UploadListResponse: + """List all in-progress multipart uploads for the authenticated organization.""" + return self._get( + "/v1/files/uploads", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UploadListResponse, + ) + + def abort( + self, + upload_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, + ) -> UploadAbortResponse: + """ + Abort a multipart upload and clean up any uploaded parts. + + Args: + upload_id: The ID of the multipart upload to abort + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 upload_id: + raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") + return self._post( + f"/v1/files/uploads/{upload_id}/abort", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UploadAbortResponse, + ) + + def complete( + self, + upload_id: str, + *, + parts: Iterable[MultipartUploadPartParam], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileObject: + """Complete a multipart upload after all parts have been uploaded. + + Creates the file + object and returns it. + + Args: + upload_id: The ID of the multipart upload + + parts: List of completed parts with their ETags + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 upload_id: + raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") + return self._post( + f"/v1/files/uploads/{upload_id}/complete", + body=maybe_transform({"parts": parts}, upload_complete_params.UploadCompleteParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileObject, + ) + + +class AsyncUploadsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncUploadsResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return AsyncUploadsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUploadsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return AsyncUploadsResourceWithStreamingResponse(self) + + async def create( + self, + *, + filename: str, + file_size: int, + mime_type: str, + part_count: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UploadCreateResponse: + """ + Initiate a multipart upload and receive presigned URLs for uploading parts + directly to storage. + + Args: + filename: Name of the file including extension + + file_size: Total size of the file in bytes + + mime_type: MIME type of the file + + part_count: Number of parts to split the upload into + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/files/uploads", + body=await async_maybe_transform( + { + "filename": filename, + "file_size": file_size, + "mime_type": mime_type, + "part_count": part_count, + }, + upload_create_params.UploadCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UploadCreateResponse, + ) + + async def retrieve( + self, + upload_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, + ) -> UploadRetrieveResponse: + """ + Get a multipart upload's details with fresh presigned URLs for any parts not yet + uploaded. + + Args: + upload_id: The ID of the multipart upload + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 upload_id: + raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") + return await self._get( + f"/v1/files/uploads/{upload_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UploadRetrieveResponse, + ) + + 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, + ) -> UploadListResponse: + """List all in-progress multipart uploads for the authenticated organization.""" + return await self._get( + "/v1/files/uploads", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UploadListResponse, + ) + + async def abort( + self, + upload_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, + ) -> UploadAbortResponse: + """ + Abort a multipart upload and clean up any uploaded parts. + + Args: + upload_id: The ID of the multipart upload to abort + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 upload_id: + raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") + return await self._post( + f"/v1/files/uploads/{upload_id}/abort", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UploadAbortResponse, + ) + + async def complete( + self, + upload_id: str, + *, + parts: Iterable[MultipartUploadPartParam], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileObject: + """Complete a multipart upload after all parts have been uploaded. + + Creates the file + object and returns it. + + Args: + upload_id: The ID of the multipart upload + + parts: List of completed parts with their ETags + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 upload_id: + raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}") + return await self._post( + f"/v1/files/uploads/{upload_id}/complete", + body=await async_maybe_transform({"parts": parts}, upload_complete_params.UploadCompleteParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileObject, + ) + + +class UploadsResourceWithRawResponse: + def __init__(self, uploads: UploadsResource) -> None: + self._uploads = uploads + + self.create = to_raw_response_wrapper( + uploads.create, + ) + self.retrieve = to_raw_response_wrapper( + uploads.retrieve, + ) + self.list = to_raw_response_wrapper( + uploads.list, + ) + self.abort = to_raw_response_wrapper( + uploads.abort, + ) + self.complete = to_raw_response_wrapper( + uploads.complete, + ) + + +class AsyncUploadsResourceWithRawResponse: + def __init__(self, uploads: AsyncUploadsResource) -> None: + self._uploads = uploads + + self.create = async_to_raw_response_wrapper( + uploads.create, + ) + self.retrieve = async_to_raw_response_wrapper( + uploads.retrieve, + ) + self.list = async_to_raw_response_wrapper( + uploads.list, + ) + self.abort = async_to_raw_response_wrapper( + uploads.abort, + ) + self.complete = async_to_raw_response_wrapper( + uploads.complete, + ) + + +class UploadsResourceWithStreamingResponse: + def __init__(self, uploads: UploadsResource) -> None: + self._uploads = uploads + + self.create = to_streamed_response_wrapper( + uploads.create, + ) + self.retrieve = to_streamed_response_wrapper( + uploads.retrieve, + ) + self.list = to_streamed_response_wrapper( + uploads.list, + ) + self.abort = to_streamed_response_wrapper( + uploads.abort, + ) + self.complete = to_streamed_response_wrapper( + uploads.complete, + ) + + +class AsyncUploadsResourceWithStreamingResponse: + def __init__(self, uploads: AsyncUploadsResource) -> None: + self._uploads = uploads + + self.create = async_to_streamed_response_wrapper( + uploads.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + uploads.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + uploads.list, + ) + self.abort = async_to_streamed_response_wrapper( + uploads.abort, + ) + self.complete = async_to_streamed_response_wrapper( + uploads.complete, + ) diff --git a/src/mixedbread/resources/parsing/jobs.py b/src/mixedbread/resources/parsing/jobs.py index ace6052c..97b70a8c 100644 --- a/src/mixedbread/resources/parsing/jobs.py +++ b/src/mixedbread/resources/parsing/jobs.py @@ -7,11 +7,8 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -20,9 +17,16 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._base_client import make_request_options -from ...types.parsing import job_create_params +from ...pagination import SyncCursor, AsyncCursor +from ..._base_client import AsyncPaginator, make_request_options +from ...types.parsing import ReturnFormat, ChunkingStrategy, job_list_params, job_create_params from ...types.parsing.parsing_job import ParsingJob +from ...types.parsing.element_type import ElementType +from ...types.parsing.return_format import ReturnFormat +from ...types.parsing.chunking_strategy import ChunkingStrategy +from ...types.parsing.job_list_response import JobListResponse +from ...types.parsing.parsing_job_status import ParsingJobStatus +from ...types.parsing.job_delete_response import JobDeleteResponse __all__ = ["JobsResource", "AsyncJobsResource"] @@ -31,10 +35,10 @@ class JobsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> JobsResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return JobsResourceWithRawResponse(self) @@ -43,7 +47,7 @@ def with_streaming_response(self) -> JobsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return JobsResourceWithStreamingResponse(self) @@ -51,32 +55,16 @@ def create( self, *, file_id: str, - chunking_strategy: Literal["page"] | NotGiven = NOT_GIVEN, - element_types: Optional[ - List[ - Literal[ - "caption", - "footnote", - "formula", - "list-item", - "page-footer", - "page-header", - "picture", - "section-header", - "table", - "text", - "title", - ] - ] - ] - | NotGiven = NOT_GIVEN, - return_format: Literal["html", "markdown", "plain"] | NotGiven = NOT_GIVEN, + element_types: Optional[List[ElementType]] | Omit = omit, + chunking_strategy: ChunkingStrategy | Omit = omit, + return_format: ReturnFormat | Omit = omit, + mode: Literal["fast", "high_quality"] | 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, ) -> ParsingJob: """ Start a parse job for the provided file. @@ -88,12 +76,14 @@ def create( Args: file_id: The ID of the file to parse - chunking_strategy: The strategy to use for chunking the content - element_types: The elements to extract from the document + chunking_strategy: The strategy to use for chunking the content + return_format: The format of the returned content + mode: The strategy to use for OCR + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -107,9 +97,10 @@ def create( body=maybe_transform( { "file_id": file_id, - "chunking_strategy": chunking_strategy, "element_types": element_types, + "chunking_strategy": chunking_strategy, "return_format": return_format, + "mode": mode, }, job_create_params.JobCreateParams, ), @@ -128,7 +119,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, ) -> ParsingJob: """ Get detailed information about a specific parse job. @@ -158,15 +149,163 @@ def retrieve( cast_to=ParsingJob, ) + def list( + self, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + statuses: Optional[List[ParsingJobStatus]] | Omit = omit, + q: 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, + ) -> SyncCursor[JobListResponse]: + """List parsing jobs with pagination. + + Args: limit: The number of items to return. + + offset: The number of items to skip. + + Returns: List of parsing jobs with pagination. + + Args: + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + statuses: Status to filter by + + q: Search query to filter by + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/parsing/jobs", + page=SyncCursor[JobListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "statuses": statuses, + "q": q, + }, + job_list_params.JobListParams, + ), + ), + model=JobListResponse, + ) + + def delete( + self, + job_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, + ) -> JobDeleteResponse: + """ + Delete a specific parse job. + + Args: job_id: The ID of the parse job to delete. + + Returns: The deleted parsing job. + + Args: + job_id: The ID of the parse job to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 job_id: + raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") + return self._delete( + f"/v1/parsing/jobs/{job_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=JobDeleteResponse, + ) + + def cancel( + self, + job_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, + ) -> ParsingJob: + """ + Cancel a specific parse job. + + Args: job_id: The ID of the parse job to cancel. + + Returns: The cancelled parsing job. + + Args: + job_id: The ID of the parse job to cancel + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 job_id: + raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") + return self._patch( + f"/v1/parsing/jobs/{job_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ParsingJob, + ) + class AsyncJobsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncJobsResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return AsyncJobsResourceWithRawResponse(self) @@ -175,7 +314,7 @@ def with_streaming_response(self) -> AsyncJobsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return AsyncJobsResourceWithStreamingResponse(self) @@ -183,32 +322,16 @@ async def create( self, *, file_id: str, - chunking_strategy: Literal["page"] | NotGiven = NOT_GIVEN, - element_types: Optional[ - List[ - Literal[ - "caption", - "footnote", - "formula", - "list-item", - "page-footer", - "page-header", - "picture", - "section-header", - "table", - "text", - "title", - ] - ] - ] - | NotGiven = NOT_GIVEN, - return_format: Literal["html", "markdown", "plain"] | NotGiven = NOT_GIVEN, + element_types: Optional[List[ElementType]] | Omit = omit, + chunking_strategy: ChunkingStrategy | Omit = omit, + return_format: ReturnFormat | Omit = omit, + mode: Literal["fast", "high_quality"] | 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, ) -> ParsingJob: """ Start a parse job for the provided file. @@ -220,12 +343,14 @@ async def create( Args: file_id: The ID of the file to parse - chunking_strategy: The strategy to use for chunking the content - element_types: The elements to extract from the document + chunking_strategy: The strategy to use for chunking the content + return_format: The format of the returned content + mode: The strategy to use for OCR + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -239,9 +364,10 @@ async def create( body=await async_maybe_transform( { "file_id": file_id, - "chunking_strategy": chunking_strategy, "element_types": element_types, + "chunking_strategy": chunking_strategy, "return_format": return_format, + "mode": mode, }, job_create_params.JobCreateParams, ), @@ -260,7 +386,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, ) -> ParsingJob: """ Get detailed information about a specific parse job. @@ -290,6 +416,154 @@ async def retrieve( cast_to=ParsingJob, ) + def list( + self, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + statuses: Optional[List[ParsingJobStatus]] | Omit = omit, + q: 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, + ) -> AsyncPaginator[JobListResponse, AsyncCursor[JobListResponse]]: + """List parsing jobs with pagination. + + Args: limit: The number of items to return. + + offset: The number of items to skip. + + Returns: List of parsing jobs with pagination. + + Args: + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + statuses: Status to filter by + + q: Search query to filter by + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/parsing/jobs", + page=AsyncCursor[JobListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "statuses": statuses, + "q": q, + }, + job_list_params.JobListParams, + ), + ), + model=JobListResponse, + ) + + async def delete( + self, + job_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, + ) -> JobDeleteResponse: + """ + Delete a specific parse job. + + Args: job_id: The ID of the parse job to delete. + + Returns: The deleted parsing job. + + Args: + job_id: The ID of the parse job to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 job_id: + raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") + return await self._delete( + f"/v1/parsing/jobs/{job_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=JobDeleteResponse, + ) + + async def cancel( + self, + job_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, + ) -> ParsingJob: + """ + Cancel a specific parse job. + + Args: job_id: The ID of the parse job to cancel. + + Returns: The cancelled parsing job. + + Args: + job_id: The ID of the parse job to cancel + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 job_id: + raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") + return await self._patch( + f"/v1/parsing/jobs/{job_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ParsingJob, + ) + class JobsResourceWithRawResponse: def __init__(self, jobs: JobsResource) -> None: @@ -301,6 +575,15 @@ def __init__(self, jobs: JobsResource) -> None: self.retrieve = to_raw_response_wrapper( jobs.retrieve, ) + self.list = to_raw_response_wrapper( + jobs.list, + ) + self.delete = to_raw_response_wrapper( + jobs.delete, + ) + self.cancel = to_raw_response_wrapper( + jobs.cancel, + ) class AsyncJobsResourceWithRawResponse: @@ -313,6 +596,15 @@ def __init__(self, jobs: AsyncJobsResource) -> None: self.retrieve = async_to_raw_response_wrapper( jobs.retrieve, ) + self.list = async_to_raw_response_wrapper( + jobs.list, + ) + self.delete = async_to_raw_response_wrapper( + jobs.delete, + ) + self.cancel = async_to_raw_response_wrapper( + jobs.cancel, + ) class JobsResourceWithStreamingResponse: @@ -325,6 +617,15 @@ def __init__(self, jobs: JobsResource) -> None: self.retrieve = to_streamed_response_wrapper( jobs.retrieve, ) + self.list = to_streamed_response_wrapper( + jobs.list, + ) + self.delete = to_streamed_response_wrapper( + jobs.delete, + ) + self.cancel = to_streamed_response_wrapper( + jobs.cancel, + ) class AsyncJobsResourceWithStreamingResponse: @@ -337,3 +638,12 @@ def __init__(self, jobs: AsyncJobsResource) -> None: self.retrieve = async_to_streamed_response_wrapper( jobs.retrieve, ) + self.list = async_to_streamed_response_wrapper( + jobs.list, + ) + self.delete = async_to_streamed_response_wrapper( + jobs.delete, + ) + self.cancel = async_to_streamed_response_wrapper( + jobs.cancel, + ) diff --git a/src/mixedbread/resources/parsing/parsing.py b/src/mixedbread/resources/parsing/parsing.py index be6f103f..069df23c 100644 --- a/src/mixedbread/resources/parsing/parsing.py +++ b/src/mixedbread/resources/parsing/parsing.py @@ -24,10 +24,10 @@ def jobs(self) -> JobsResource: @cached_property def with_raw_response(self) -> ParsingResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return ParsingResourceWithRawResponse(self) @@ -36,7 +36,7 @@ def with_streaming_response(self) -> ParsingResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return ParsingResourceWithStreamingResponse(self) @@ -49,10 +49,10 @@ def jobs(self) -> AsyncJobsResource: @cached_property def with_raw_response(self) -> AsyncParsingResourceWithRawResponse: """ - 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/stainless-sdks/mixedbread-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers """ return AsyncParsingResourceWithRawResponse(self) @@ -61,7 +61,7 @@ def with_streaming_response(self) -> AsyncParsingResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response """ return AsyncParsingResourceWithStreamingResponse(self) diff --git a/src/mixedbread/resources/reranking.py b/src/mixedbread/resources/reranking.py deleted file mode 100644 index 73185c1c..00000000 --- a/src/mixedbread/resources/reranking.py +++ /dev/null @@ -1,230 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Union, Optional - -import httpx - -from ..types import reranking_create_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.reranking_create_response import RerankingCreateResponse - -__all__ = ["RerankingResource", "AsyncRerankingResource"] - - -class RerankingResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> RerankingResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return RerankingResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> RerankingResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return RerankingResourceWithStreamingResponse(self) - - def create( - self, - *, - input: List[Union[str, object]], - query: str, - model: str | NotGiven = NOT_GIVEN, - rank_fields: Optional[List[str]] | NotGiven = NOT_GIVEN, - return_input: bool | NotGiven = NOT_GIVEN, - top_k: 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, - ) -> RerankingCreateResponse: - """ - Rerank different kind of documents for a given query. - - Args: params: RerankingCreateParams: The parameters for reranking. - - Returns: RerankingCreateResponse: The reranked documents for the input query. - - Args: - input: The input documents to rerank. - - query: The query to rerank the documents. - - model: The model to use for reranking documents. - - rank_fields: The fields of the documents to rank. - - return_input: Whether to return the documents. - - top_k: The number of documents to return. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/reranking", - body=maybe_transform( - { - "input": input, - "query": query, - "model": model, - "rank_fields": rank_fields, - "return_input": return_input, - "top_k": top_k, - }, - reranking_create_params.RerankingCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=RerankingCreateResponse, - ) - - -class AsyncRerankingResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncRerankingResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return AsyncRerankingResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncRerankingResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return AsyncRerankingResourceWithStreamingResponse(self) - - async def create( - self, - *, - input: List[Union[str, object]], - query: str, - model: str | NotGiven = NOT_GIVEN, - rank_fields: Optional[List[str]] | NotGiven = NOT_GIVEN, - return_input: bool | NotGiven = NOT_GIVEN, - top_k: 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, - ) -> RerankingCreateResponse: - """ - Rerank different kind of documents for a given query. - - Args: params: RerankingCreateParams: The parameters for reranking. - - Returns: RerankingCreateResponse: The reranked documents for the input query. - - Args: - input: The input documents to rerank. - - query: The query to rerank the documents. - - model: The model to use for reranking documents. - - rank_fields: The fields of the documents to rank. - - return_input: Whether to return the documents. - - top_k: The number of documents to return. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/reranking", - body=await async_maybe_transform( - { - "input": input, - "query": query, - "model": model, - "rank_fields": rank_fields, - "return_input": return_input, - "top_k": top_k, - }, - reranking_create_params.RerankingCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=RerankingCreateResponse, - ) - - -class RerankingResourceWithRawResponse: - def __init__(self, reranking: RerankingResource) -> None: - self._reranking = reranking - - self.create = to_raw_response_wrapper( - reranking.create, - ) - - -class AsyncRerankingResourceWithRawResponse: - def __init__(self, reranking: AsyncRerankingResource) -> None: - self._reranking = reranking - - self.create = async_to_raw_response_wrapper( - reranking.create, - ) - - -class RerankingResourceWithStreamingResponse: - def __init__(self, reranking: RerankingResource) -> None: - self._reranking = reranking - - self.create = to_streamed_response_wrapper( - reranking.create, - ) - - -class AsyncRerankingResourceWithStreamingResponse: - def __init__(self, reranking: AsyncRerankingResource) -> None: - self._reranking = reranking - - self.create = async_to_streamed_response_wrapper( - reranking.create, - ) diff --git a/src/mixedbread/resources/service_info.py b/src/mixedbread/resources/service_info.py deleted file mode 100644 index 6ca3b45f..00000000 --- a/src/mixedbread/resources/service_info.py +++ /dev/null @@ -1,143 +0,0 @@ -# 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.info_response import InfoResponse - -__all__ = ["ServiceInfoResource", "AsyncServiceInfoResource"] - - -class ServiceInfoResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> ServiceInfoResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return ServiceInfoResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ServiceInfoResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return ServiceInfoResourceWithStreamingResponse(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, - ) -> InfoResponse: - """ - Returns service information, including name and version. - - Returns: InfoResponse: A response containing the service name and version. - """ - return self._get( - "/", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=InfoResponse, - ) - - -class AsyncServiceInfoResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncServiceInfoResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return AsyncServiceInfoResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncServiceInfoResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return AsyncServiceInfoResourceWithStreamingResponse(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, - ) -> InfoResponse: - """ - Returns service information, including name and version. - - Returns: InfoResponse: A response containing the service name and version. - """ - return await self._get( - "/", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=InfoResponse, - ) - - -class ServiceInfoResourceWithRawResponse: - def __init__(self, service_info: ServiceInfoResource) -> None: - self._service_info = service_info - - self.retrieve = to_raw_response_wrapper( - service_info.retrieve, - ) - - -class AsyncServiceInfoResourceWithRawResponse: - def __init__(self, service_info: AsyncServiceInfoResource) -> None: - self._service_info = service_info - - self.retrieve = async_to_raw_response_wrapper( - service_info.retrieve, - ) - - -class ServiceInfoResourceWithStreamingResponse: - def __init__(self, service_info: ServiceInfoResource) -> None: - self._service_info = service_info - - self.retrieve = to_streamed_response_wrapper( - service_info.retrieve, - ) - - -class AsyncServiceInfoResourceWithStreamingResponse: - def __init__(self, service_info: AsyncServiceInfoResource) -> None: - self._service_info = service_info - - self.retrieve = async_to_streamed_response_wrapper( - service_info.retrieve, - ) diff --git a/src/mixedbread/resources/vector_stores/__init__.py b/src/mixedbread/resources/stores/__init__.py similarity index 50% rename from src/mixedbread/resources/vector_stores/__init__.py rename to src/mixedbread/resources/stores/__init__.py index 85d202da..8ec1ec90 100644 --- a/src/mixedbread/resources/vector_stores/__init__.py +++ b/src/mixedbread/resources/stores/__init__.py @@ -8,13 +8,13 @@ FilesResourceWithStreamingResponse, AsyncFilesResourceWithStreamingResponse, ) -from .vector_stores import ( - VectorStoresResource, - AsyncVectorStoresResource, - VectorStoresResourceWithRawResponse, - AsyncVectorStoresResourceWithRawResponse, - VectorStoresResourceWithStreamingResponse, - AsyncVectorStoresResourceWithStreamingResponse, +from .stores import ( + StoresResource, + AsyncStoresResource, + StoresResourceWithRawResponse, + AsyncStoresResourceWithRawResponse, + StoresResourceWithStreamingResponse, + AsyncStoresResourceWithStreamingResponse, ) __all__ = [ @@ -24,10 +24,10 @@ "AsyncFilesResourceWithRawResponse", "FilesResourceWithStreamingResponse", "AsyncFilesResourceWithStreamingResponse", - "VectorStoresResource", - "AsyncVectorStoresResource", - "VectorStoresResourceWithRawResponse", - "AsyncVectorStoresResourceWithRawResponse", - "VectorStoresResourceWithStreamingResponse", - "AsyncVectorStoresResourceWithStreamingResponse", + "StoresResource", + "AsyncStoresResource", + "StoresResourceWithRawResponse", + "AsyncStoresResourceWithRawResponse", + "StoresResourceWithStreamingResponse", + "AsyncStoresResourceWithStreamingResponse", ] diff --git a/src/mixedbread/resources/stores/files.py b/src/mixedbread/resources/stores/files.py new file mode 100644 index 00000000..87b884a9 --- /dev/null +++ b/src/mixedbread/resources/stores/files.py @@ -0,0 +1,891 @@ +# 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 + +import httpx + +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 +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.stores import ( + file_list_params, + file_create_params, + file_search_params, + file_update_params, + file_retrieve_params, +) +from ...types.stores.store_file import StoreFile +from ...types.stores.store_file_status import StoreFileStatus +from ...types.stores.file_list_response import FileListResponse +from ...types.stores.file_delete_response import FileDeleteResponse +from ...types.stores.file_search_response import FileSearchResponse + +__all__ = ["FilesResource", "AsyncFilesResource"] + + +class FilesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> FilesResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return FilesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FilesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return FilesResourceWithStreamingResponse(self) + + def create( + self, + store_identifier: str, + *, + metadata: object | Omit = omit, + config: file_create_params.Config | Omit = omit, + external_id: Optional[str] | Omit = omit, + overwrite: bool | Omit = omit, + file_id: str, + experimental: Optional[file_create_params.Experimental] | 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, + ) -> StoreFile: + """Upload a file to a store. + + Args: store_identifier: The ID or name of the store. + + file_add_params: The file + to add to the store. + + Returns: VectorStoreFile: The uploaded file details. + + Args: + store_identifier: The ID or name of the store + + metadata: Optional metadata for the file + + config: Configuration for adding the file + + external_id: External identifier for this file in the store + + overwrite: If true, overwrite an existing file with the same external_id + + file_id: ID of the file to add + + experimental: Configuration for a file. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._post( + f"/v1/stores/{store_identifier}/files", + body=maybe_transform( + { + "metadata": metadata, + "config": config, + "external_id": external_id, + "overwrite": overwrite, + "file_id": file_id, + "experimental": experimental, + }, + file_create_params.FileCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreFile, + ) + + def retrieve( + self, + file_identifier: str, + *, + store_identifier: str, + return_chunks: Union[bool, Iterable[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, + ) -> StoreFile: + """Get a file from a store. + + Args: store_identifier: The ID or name of the store. + + file_id: The ID or name of + the file. options: Get file options. + + Returns: VectorStoreFile: The file details. + + Args: + store_identifier: The ID or name of the store + + file_identifier: The ID or name of the file + + return_chunks: Whether to return the chunks for the file. If a list of integers is provided, + only the chunks at the specified indices will be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_identifier: + raise ValueError(f"Expected a non-empty value for `file_identifier` but received {file_identifier!r}") + return self._get( + f"/v1/stores/{store_identifier}/files/{file_identifier}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"return_chunks": return_chunks}, file_retrieve_params.FileRetrieveParams), + ), + cast_to=StoreFile, + ) + + def update( + self, + file_identifier: str, + *, + store_identifier: str, + metadata: Optional[Dict[str, object]] | 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, + ) -> StoreFile: + """ + Update metadata on a file within a store. + + Args: store_identifier: The ID or name of the store. file_identifier: The ID or + name of the file to update. update_params: Metadata update payload. + + Returns: StoreFile: The updated file details. + + Args: + store_identifier: The ID or name of the store + + file_identifier: The ID or name of the file to update + + metadata: Updated metadata for the file + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_identifier: + raise ValueError(f"Expected a non-empty value for `file_identifier` but received {file_identifier!r}") + return self._patch( + f"/v1/stores/{store_identifier}/files/{file_identifier}", + body=maybe_transform({"metadata": metadata}, file_update_params.FileUpdateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreFile, + ) + + def list( + self, + store_identifier: str, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + statuses: Optional[List[StoreFileStatus]] | Omit = omit, + metadata_filter: Optional[file_list_params.MetadataFilter] | Omit = omit, + q: 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, + ) -> FileListResponse: + """ + List files indexed in a vector store with pagination and metadata filter. + + Args: vector_store_identifier: The ID or name of the vector store pagination: + Pagination parameters and metadata filter + + Returns: VectorStoreFileListResponse: Paginated list of vector store files + + Args: + store_identifier: The ID or name of the store + + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + statuses: Status to filter by + + metadata_filter: Metadata filter to apply to the query + + q: Search query for fuzzy matching over name and external_id fields + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._post( + f"/v1/stores/{store_identifier}/files/list", + body=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "statuses": statuses, + "metadata_filter": metadata_filter, + "q": q, + }, + file_list_params.FileListParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileListResponse, + ) + + def delete( + self, + file_identifier: str, + *, + store_identifier: 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, + ) -> FileDeleteResponse: + """Delete a file from a store. + + Args: store_identifier: The ID or name of the store. + + file_id: The ID or name of + the file to delete. + + Returns: VectorStoreFileDeleted: The deleted file details. + + Args: + store_identifier: The ID or name of the store + + file_identifier: The ID or name of the file to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_identifier: + raise ValueError(f"Expected a non-empty value for `file_identifier` but received {file_identifier!r}") + return self._delete( + f"/v1/stores/{store_identifier}/files/{file_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileDeleteResponse, + ) + + def search( + self, + *, + query: file_search_params.Query, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[file_search_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: file_search_params.SearchOptions | 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, + ) -> FileSearchResponse: + """ + Search for files within a store based on semantic similarity. + + Args: store_identifier: The ID or name of the store to search within + search_params: Search configuration including query text, pagination, and + filters + + Returns: StoreFileSearchResponse: List of matching files with relevance scores + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores/files/search", + body=maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + }, + file_search_params.FileSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileSearchResponse, + ) + + +class AsyncFilesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncFilesResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return AsyncFilesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFilesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return AsyncFilesResourceWithStreamingResponse(self) + + async def create( + self, + store_identifier: str, + *, + metadata: object | Omit = omit, + config: file_create_params.Config | Omit = omit, + external_id: Optional[str] | Omit = omit, + overwrite: bool | Omit = omit, + file_id: str, + experimental: Optional[file_create_params.Experimental] | 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, + ) -> StoreFile: + """Upload a file to a store. + + Args: store_identifier: The ID or name of the store. + + file_add_params: The file + to add to the store. + + Returns: VectorStoreFile: The uploaded file details. + + Args: + store_identifier: The ID or name of the store + + metadata: Optional metadata for the file + + config: Configuration for adding the file + + external_id: External identifier for this file in the store + + overwrite: If true, overwrite an existing file with the same external_id + + file_id: ID of the file to add + + experimental: Configuration for a file. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._post( + f"/v1/stores/{store_identifier}/files", + body=await async_maybe_transform( + { + "metadata": metadata, + "config": config, + "external_id": external_id, + "overwrite": overwrite, + "file_id": file_id, + "experimental": experimental, + }, + file_create_params.FileCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreFile, + ) + + async def retrieve( + self, + file_identifier: str, + *, + store_identifier: str, + return_chunks: Union[bool, Iterable[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, + ) -> StoreFile: + """Get a file from a store. + + Args: store_identifier: The ID or name of the store. + + file_id: The ID or name of + the file. options: Get file options. + + Returns: VectorStoreFile: The file details. + + Args: + store_identifier: The ID or name of the store + + file_identifier: The ID or name of the file + + return_chunks: Whether to return the chunks for the file. If a list of integers is provided, + only the chunks at the specified indices will be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_identifier: + raise ValueError(f"Expected a non-empty value for `file_identifier` but received {file_identifier!r}") + return await self._get( + f"/v1/stores/{store_identifier}/files/{file_identifier}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"return_chunks": return_chunks}, file_retrieve_params.FileRetrieveParams + ), + ), + cast_to=StoreFile, + ) + + async def update( + self, + file_identifier: str, + *, + store_identifier: str, + metadata: Optional[Dict[str, object]] | 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, + ) -> StoreFile: + """ + Update metadata on a file within a store. + + Args: store_identifier: The ID or name of the store. file_identifier: The ID or + name of the file to update. update_params: Metadata update payload. + + Returns: StoreFile: The updated file details. + + Args: + store_identifier: The ID or name of the store + + file_identifier: The ID or name of the file to update + + metadata: Updated metadata for the file + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_identifier: + raise ValueError(f"Expected a non-empty value for `file_identifier` but received {file_identifier!r}") + return await self._patch( + f"/v1/stores/{store_identifier}/files/{file_identifier}", + body=await async_maybe_transform({"metadata": metadata}, file_update_params.FileUpdateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreFile, + ) + + async def list( + self, + store_identifier: str, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + statuses: Optional[List[StoreFileStatus]] | Omit = omit, + metadata_filter: Optional[file_list_params.MetadataFilter] | Omit = omit, + q: 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, + ) -> FileListResponse: + """ + List files indexed in a vector store with pagination and metadata filter. + + Args: vector_store_identifier: The ID or name of the vector store pagination: + Pagination parameters and metadata filter + + Returns: VectorStoreFileListResponse: Paginated list of vector store files + + Args: + store_identifier: The ID or name of the store + + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + statuses: Status to filter by + + metadata_filter: Metadata filter to apply to the query + + q: Search query for fuzzy matching over name and external_id fields + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._post( + f"/v1/stores/{store_identifier}/files/list", + body=await async_maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "statuses": statuses, + "metadata_filter": metadata_filter, + "q": q, + }, + file_list_params.FileListParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileListResponse, + ) + + async def delete( + self, + file_identifier: str, + *, + store_identifier: 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, + ) -> FileDeleteResponse: + """Delete a file from a store. + + Args: store_identifier: The ID or name of the store. + + file_id: The ID or name of + the file to delete. + + Returns: VectorStoreFileDeleted: The deleted file details. + + Args: + store_identifier: The ID or name of the store + + file_identifier: The ID or name of the file to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_identifier: + raise ValueError(f"Expected a non-empty value for `file_identifier` but received {file_identifier!r}") + return await self._delete( + f"/v1/stores/{store_identifier}/files/{file_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileDeleteResponse, + ) + + async def search( + self, + *, + query: file_search_params.Query, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[file_search_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: file_search_params.SearchOptions | 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, + ) -> FileSearchResponse: + """ + Search for files within a store based on semantic similarity. + + Args: store_identifier: The ID or name of the store to search within + search_params: Search configuration including query text, pagination, and + filters + + Returns: StoreFileSearchResponse: List of matching files with relevance scores + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores/files/search", + body=await async_maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + }, + file_search_params.FileSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileSearchResponse, + ) + + +class FilesResourceWithRawResponse: + def __init__(self, files: FilesResource) -> None: + self._files = files + + self.create = to_raw_response_wrapper( + files.create, + ) + self.retrieve = to_raw_response_wrapper( + files.retrieve, + ) + self.update = to_raw_response_wrapper( + files.update, + ) + self.list = to_raw_response_wrapper( + files.list, + ) + self.delete = to_raw_response_wrapper( + files.delete, + ) + self.search = to_raw_response_wrapper( + files.search, + ) + + +class AsyncFilesResourceWithRawResponse: + def __init__(self, files: AsyncFilesResource) -> None: + self._files = files + + self.create = async_to_raw_response_wrapper( + files.create, + ) + self.retrieve = async_to_raw_response_wrapper( + files.retrieve, + ) + self.update = async_to_raw_response_wrapper( + files.update, + ) + self.list = async_to_raw_response_wrapper( + files.list, + ) + self.delete = async_to_raw_response_wrapper( + files.delete, + ) + self.search = async_to_raw_response_wrapper( + files.search, + ) + + +class FilesResourceWithStreamingResponse: + def __init__(self, files: FilesResource) -> None: + self._files = files + + self.create = to_streamed_response_wrapper( + files.create, + ) + self.retrieve = to_streamed_response_wrapper( + files.retrieve, + ) + self.update = to_streamed_response_wrapper( + files.update, + ) + self.list = to_streamed_response_wrapper( + files.list, + ) + self.delete = to_streamed_response_wrapper( + files.delete, + ) + self.search = to_streamed_response_wrapper( + files.search, + ) + + +class AsyncFilesResourceWithStreamingResponse: + def __init__(self, files: AsyncFilesResource) -> None: + self._files = files + + self.create = async_to_streamed_response_wrapper( + files.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + files.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + files.update, + ) + self.list = async_to_streamed_response_wrapper( + files.list, + ) + self.delete = async_to_streamed_response_wrapper( + files.delete, + ) + self.search = async_to_streamed_response_wrapper( + files.search, + ) diff --git a/src/mixedbread/resources/stores/stores.py b/src/mixedbread/resources/stores/stores.py new file mode 100644 index 00000000..41eac718 --- /dev/null +++ b/src/mixedbread/resources/stores/stores.py @@ -0,0 +1,1213 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional + +import httpx + +from .files import ( + FilesResource, + AsyncFilesResource, + FilesResourceWithRawResponse, + AsyncFilesResourceWithRawResponse, + FilesResourceWithStreamingResponse, + AsyncFilesResourceWithStreamingResponse, +) +from ...types import ( + store_list_params, + store_create_params, + store_search_params, + store_update_params, + store_metadata_facets_params, + store_question_answering_params, +) +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 +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...pagination import SyncCursor, AsyncCursor +from ...types.store import Store +from ..._base_client import AsyncPaginator, make_request_options +from ...types.expires_after_param import ExpiresAfterParam +from ...types.store_delete_response import StoreDeleteResponse +from ...types.store_search_response import StoreSearchResponse +from ...types.store_metadata_facets_response import StoreMetadataFacetsResponse +from ...types.store_chunk_search_options_param import StoreChunkSearchOptionsParam +from ...types.store_question_answering_response import StoreQuestionAnsweringResponse + +__all__ = ["StoresResource", "AsyncStoresResource"] + + +class StoresResource(SyncAPIResource): + @cached_property + def files(self) -> FilesResource: + return FilesResource(self._client) + + @cached_property + def with_raw_response(self) -> StoresResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return StoresResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> StoresResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return StoresResourceWithStreamingResponse(self) + + def create( + self, + *, + name: Optional[str] | Omit = omit, + description: Optional[str] | Omit = omit, + is_public: bool | Omit = omit, + expires_after: Optional[ExpiresAfterParam] | Omit = omit, + metadata: object | Omit = omit, + config: Optional[store_create_params.Config] | Omit = omit, + file_ids: Optional[SequenceNotStr[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, + ) -> Store: + """ + Create a new vector store. + + Args: vector_store_create: VectorStoreCreate object containing the name, + description, and metadata. + + Returns: VectorStore: The response containing the created vector store details. + + Args: + name: Name for the new store. Can only contain lowercase letters, numbers, periods + (.), and hyphens (-). + + description: Description of the store + + is_public: Whether the store can be accessed by anyone with valid login credentials + + expires_after: Represents an expiration policy for a store. + + metadata: Optional metadata key-value pairs + + config: Configuration for a store. + + file_ids: Optional list of file IDs + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores", + body=maybe_transform( + { + "name": name, + "description": description, + "is_public": is_public, + "expires_after": expires_after, + "metadata": metadata, + "config": config, + "file_ids": file_ids, + }, + store_create_params.StoreCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + def retrieve( + self, + store_identifier: 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, + ) -> Store: + """ + Get a store by ID or name. + + Args: store_identifier: The ID or name of the store to retrieve. + + Returns: Store: The response containing the store details. + + Args: + store_identifier: The ID or name of the store + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._get( + f"/v1/stores/{store_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + def update( + self, + store_identifier: str, + *, + name: Optional[str] | Omit = omit, + description: Optional[str] | Omit = omit, + is_public: Optional[bool] | Omit = omit, + expires_after: Optional[ExpiresAfterParam] | Omit = omit, + metadata: object | 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, + ) -> Store: + """ + Update a store by ID or name. + + Args: store_identifier: The ID or name of the store to update. store_update: + StoreCreate object containing the name, description, and metadata. + + Returns: Store: The response containing the updated store details. + + Args: + store_identifier: The ID or name of the store + + name: New name for the store. Can only contain lowercase letters, numbers, periods + (.), and hyphens (-). + + description: New description + + is_public: Whether the store can be accessed by anyone with valid login credentials + + expires_after: Represents an expiration policy for a store. + + metadata: Optional metadata key-value pairs + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._put( + f"/v1/stores/{store_identifier}", + body=maybe_transform( + { + "name": name, + "description": description, + "is_public": is_public, + "expires_after": expires_after, + "metadata": metadata, + }, + store_update_params.StoreUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + def list( + self, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + q: 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, + ) -> SyncCursor[Store]: + """List all stores with optional search. + + Args: pagination: The pagination options. + + q: Optional search query to filter + vector stores. + + Returns: StoreListResponse: The list of stores. + + Args: + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + q: Search query for fuzzy matching over name and description fields + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/stores", + page=SyncCursor[Store], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "q": q, + }, + store_list_params.StoreListParams, + ), + ), + model=Store, + ) + + def delete( + self, + store_identifier: 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, + ) -> StoreDeleteResponse: + """ + Delete a store by ID or name. + + Args: store_identifier: The ID or name of the store to delete. + + Returns: Store: The response containing the deleted store details. + + Args: + store_identifier: The ID or name of the store to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._delete( + f"/v1/stores/{store_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreDeleteResponse, + ) + + def metadata_facets( + self, + *, + query: Optional[str] | Omit = omit, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_metadata_facets_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | Omit = omit, + facets: Optional[SequenceNotStr[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, + ) -> StoreMetadataFacetsResponse: + """ + Get metadata facets + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + facets: Optional list of facets to return. Use dot for nested fields. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores/metadata-facets", + body=maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + "facets": facets, + }, + store_metadata_facets_params.StoreMetadataFacetsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreMetadataFacetsResponse, + ) + + def question_answering( + self, + *, + query: str | Omit = omit, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_question_answering_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | Omit = omit, + stream: bool | Omit = omit, + qa_options: store_question_answering_params.QaOptions | 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, + ) -> StoreQuestionAnsweringResponse: + """Question answering + + Args: + query: Question to answer. + + If not provided, the question will be extracted from the + passed messages. + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + stream: Whether to stream the answer + + qa_options: Question answering configuration options + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores/question-answering", + body=maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + "stream": stream, + "qa_options": qa_options, + }, + store_question_answering_params.StoreQuestionAnsweringParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreQuestionAnsweringResponse, + ) + + def search( + self, + *, + query: store_search_params.Query, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_search_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | 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, + ) -> StoreSearchResponse: + """ + Perform semantic search across store chunks. + + This endpoint searches through store chunks using semantic similarity matching. + It supports complex search queries with filters and returns relevance-scored + results. + + For the special 'mixedbread/web' store, this endpoint performs web search using + a mixture of different providers instead of semantic search. Web search results + are always reranked for consistent scoring. + + Args: search_params: Search configuration including: - query text or + embeddings - store_identifiers: List of store identifiers to search - file_ids: + Optional list of file IDs to filter chunks by (or tuple of list and condition + operator) - metadata filters - pagination parameters - sorting preferences + \\__state: API state dependency \\__ctx: Service context dependency + + Returns: StoreSearchResponse containing: - List of matched chunks with relevance + scores - Pagination details including total result count + + Raises: HTTPException (400): If search parameters are invalid HTTPException + (404): If no vector stores are found to search + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores/search", + body=maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + }, + store_search_params.StoreSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreSearchResponse, + ) + + +class AsyncStoresResource(AsyncAPIResource): + @cached_property + def files(self) -> AsyncFilesResource: + return AsyncFilesResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncStoresResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return AsyncStoresResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncStoresResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return AsyncStoresResourceWithStreamingResponse(self) + + async def create( + self, + *, + name: Optional[str] | Omit = omit, + description: Optional[str] | Omit = omit, + is_public: bool | Omit = omit, + expires_after: Optional[ExpiresAfterParam] | Omit = omit, + metadata: object | Omit = omit, + config: Optional[store_create_params.Config] | Omit = omit, + file_ids: Optional[SequenceNotStr[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, + ) -> Store: + """ + Create a new vector store. + + Args: vector_store_create: VectorStoreCreate object containing the name, + description, and metadata. + + Returns: VectorStore: The response containing the created vector store details. + + Args: + name: Name for the new store. Can only contain lowercase letters, numbers, periods + (.), and hyphens (-). + + description: Description of the store + + is_public: Whether the store can be accessed by anyone with valid login credentials + + expires_after: Represents an expiration policy for a store. + + metadata: Optional metadata key-value pairs + + config: Configuration for a store. + + file_ids: Optional list of file IDs + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores", + body=await async_maybe_transform( + { + "name": name, + "description": description, + "is_public": is_public, + "expires_after": expires_after, + "metadata": metadata, + "config": config, + "file_ids": file_ids, + }, + store_create_params.StoreCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + async def retrieve( + self, + store_identifier: 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, + ) -> Store: + """ + Get a store by ID or name. + + Args: store_identifier: The ID or name of the store to retrieve. + + Returns: Store: The response containing the store details. + + Args: + store_identifier: The ID or name of the store + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._get( + f"/v1/stores/{store_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + async def update( + self, + store_identifier: str, + *, + name: Optional[str] | Omit = omit, + description: Optional[str] | Omit = omit, + is_public: Optional[bool] | Omit = omit, + expires_after: Optional[ExpiresAfterParam] | Omit = omit, + metadata: object | 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, + ) -> Store: + """ + Update a store by ID or name. + + Args: store_identifier: The ID or name of the store to update. store_update: + StoreCreate object containing the name, description, and metadata. + + Returns: Store: The response containing the updated store details. + + Args: + store_identifier: The ID or name of the store + + name: New name for the store. Can only contain lowercase letters, numbers, periods + (.), and hyphens (-). + + description: New description + + is_public: Whether the store can be accessed by anyone with valid login credentials + + expires_after: Represents an expiration policy for a store. + + metadata: Optional metadata key-value pairs + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._put( + f"/v1/stores/{store_identifier}", + body=await async_maybe_transform( + { + "name": name, + "description": description, + "is_public": is_public, + "expires_after": expires_after, + "metadata": metadata, + }, + store_update_params.StoreUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + def list( + self, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + q: 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, + ) -> AsyncPaginator[Store, AsyncCursor[Store]]: + """List all stores with optional search. + + Args: pagination: The pagination options. + + q: Optional search query to filter + vector stores. + + Returns: StoreListResponse: The list of stores. + + Args: + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + q: Search query for fuzzy matching over name and description fields + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/stores", + page=AsyncCursor[Store], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "q": q, + }, + store_list_params.StoreListParams, + ), + ), + model=Store, + ) + + async def delete( + self, + store_identifier: 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, + ) -> StoreDeleteResponse: + """ + Delete a store by ID or name. + + Args: store_identifier: The ID or name of the store to delete. + + Returns: Store: The response containing the deleted store details. + + Args: + store_identifier: The ID or name of the store to delete + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._delete( + f"/v1/stores/{store_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreDeleteResponse, + ) + + async def metadata_facets( + self, + *, + query: Optional[str] | Omit = omit, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_metadata_facets_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | Omit = omit, + facets: Optional[SequenceNotStr[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, + ) -> StoreMetadataFacetsResponse: + """ + Get metadata facets + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + facets: Optional list of facets to return. Use dot for nested fields. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores/metadata-facets", + body=await async_maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + "facets": facets, + }, + store_metadata_facets_params.StoreMetadataFacetsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreMetadataFacetsResponse, + ) + + async def question_answering( + self, + *, + query: str | Omit = omit, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_question_answering_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | Omit = omit, + stream: bool | Omit = omit, + qa_options: store_question_answering_params.QaOptions | 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, + ) -> StoreQuestionAnsweringResponse: + """Question answering + + Args: + query: Question to answer. + + If not provided, the question will be extracted from the + passed messages. + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + stream: Whether to stream the answer + + qa_options: Question answering configuration options + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores/question-answering", + body=await async_maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + "stream": stream, + "qa_options": qa_options, + }, + store_question_answering_params.StoreQuestionAnsweringParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreQuestionAnsweringResponse, + ) + + async def search( + self, + *, + query: store_search_params.Query, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_search_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | 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, + ) -> StoreSearchResponse: + """ + Perform semantic search across store chunks. + + This endpoint searches through store chunks using semantic similarity matching. + It supports complex search queries with filters and returns relevance-scored + results. + + For the special 'mixedbread/web' store, this endpoint performs web search using + a mixture of different providers instead of semantic search. Web search results + are always reranked for consistent scoring. + + Args: search_params: Search configuration including: - query text or + embeddings - store_identifiers: List of store identifiers to search - file_ids: + Optional list of file IDs to filter chunks by (or tuple of list and condition + operator) - metadata filters - pagination parameters - sorting preferences + \\__state: API state dependency \\__ctx: Service context dependency + + Returns: StoreSearchResponse containing: - List of matched chunks with relevance + scores - Pagination details including total result count + + Raises: HTTPException (400): If search parameters are invalid HTTPException + (404): If no vector stores are found to search + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + extra_headers: Send extra headers + + extra_query: Add additional query parameters 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( + "/v1/stores/search", + body=await async_maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + }, + store_search_params.StoreSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreSearchResponse, + ) + + +class StoresResourceWithRawResponse: + def __init__(self, stores: StoresResource) -> None: + self._stores = stores + + self.create = to_raw_response_wrapper( + stores.create, + ) + self.retrieve = to_raw_response_wrapper( + stores.retrieve, + ) + self.update = to_raw_response_wrapper( + stores.update, + ) + self.list = to_raw_response_wrapper( + stores.list, + ) + self.delete = to_raw_response_wrapper( + stores.delete, + ) + self.metadata_facets = to_raw_response_wrapper( + stores.metadata_facets, + ) + self.question_answering = to_raw_response_wrapper( + stores.question_answering, + ) + self.search = to_raw_response_wrapper( + stores.search, + ) + + @cached_property + def files(self) -> FilesResourceWithRawResponse: + return FilesResourceWithRawResponse(self._stores.files) + + +class AsyncStoresResourceWithRawResponse: + def __init__(self, stores: AsyncStoresResource) -> None: + self._stores = stores + + self.create = async_to_raw_response_wrapper( + stores.create, + ) + self.retrieve = async_to_raw_response_wrapper( + stores.retrieve, + ) + self.update = async_to_raw_response_wrapper( + stores.update, + ) + self.list = async_to_raw_response_wrapper( + stores.list, + ) + self.delete = async_to_raw_response_wrapper( + stores.delete, + ) + self.metadata_facets = async_to_raw_response_wrapper( + stores.metadata_facets, + ) + self.question_answering = async_to_raw_response_wrapper( + stores.question_answering, + ) + self.search = async_to_raw_response_wrapper( + stores.search, + ) + + @cached_property + def files(self) -> AsyncFilesResourceWithRawResponse: + return AsyncFilesResourceWithRawResponse(self._stores.files) + + +class StoresResourceWithStreamingResponse: + def __init__(self, stores: StoresResource) -> None: + self._stores = stores + + self.create = to_streamed_response_wrapper( + stores.create, + ) + self.retrieve = to_streamed_response_wrapper( + stores.retrieve, + ) + self.update = to_streamed_response_wrapper( + stores.update, + ) + self.list = to_streamed_response_wrapper( + stores.list, + ) + self.delete = to_streamed_response_wrapper( + stores.delete, + ) + self.metadata_facets = to_streamed_response_wrapper( + stores.metadata_facets, + ) + self.question_answering = to_streamed_response_wrapper( + stores.question_answering, + ) + self.search = to_streamed_response_wrapper( + stores.search, + ) + + @cached_property + def files(self) -> FilesResourceWithStreamingResponse: + return FilesResourceWithStreamingResponse(self._stores.files) + + +class AsyncStoresResourceWithStreamingResponse: + def __init__(self, stores: AsyncStoresResource) -> None: + self._stores = stores + + self.create = async_to_streamed_response_wrapper( + stores.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + stores.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + stores.update, + ) + self.list = async_to_streamed_response_wrapper( + stores.list, + ) + self.delete = async_to_streamed_response_wrapper( + stores.delete, + ) + self.metadata_facets = async_to_streamed_response_wrapper( + stores.metadata_facets, + ) + self.question_answering = async_to_streamed_response_wrapper( + stores.question_answering, + ) + self.search = async_to_streamed_response_wrapper( + stores.search, + ) + + @cached_property + def files(self) -> AsyncFilesResourceWithStreamingResponse: + return AsyncFilesResourceWithStreamingResponse(self._stores.files) diff --git a/src/mixedbread/resources/vector_stores/files.py b/src/mixedbread/resources/vector_stores/files.py deleted file mode 100644 index cc7f699c..00000000 --- a/src/mixedbread/resources/vector_stores/files.py +++ /dev/null @@ -1,683 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List - -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.vector_stores import file_list_params, file_create_params, file_search_params -from ...types.vector_stores.vector_store_file import VectorStoreFile -from ...types.vector_stores.file_list_response import FileListResponse -from ...types.vector_stores.file_search_response import FileSearchResponse -from ...types.vector_stores.vector_store_file_deleted import VectorStoreFileDeleted - -__all__ = ["FilesResource", "AsyncFilesResource"] - - -class FilesResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> FilesResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return FilesResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> FilesResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return FilesResourceWithStreamingResponse(self) - - def create( - self, - vector_store_id: str, - *, - file_id: str, - metadata: object | 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, - ) -> VectorStoreFile: - """ - Upload a new file to a vector store for indexing. - - Args: vector_store_id: The ID of the vector store to upload to file: The file to - upload and index - - Returns: VectorStoreFile: Details of the uploaded and indexed file - - Args: - vector_store_id: The ID of the vector store - - file_id: ID of the file to add - - metadata: Optional metadata for the file - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return self._post( - f"/v1/vector_stores/{vector_store_id}/files", - body=maybe_transform( - { - "file_id": file_id, - "metadata": metadata, - }, - file_create_params.FileCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreFile, - ) - - def retrieve( - self, - file_id: str, - *, - vector_store_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, - ) -> VectorStoreFile: - """ - Get details of a specific file in a vector store. - - Args: vector_store_id: The ID of the vector store file_id: The ID of the file - - Returns: VectorStoreFile: Details of the vector store file - - Args: - vector_store_id: The ID of the vector store - - file_id: The ID of the file - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - if not file_id: - raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") - return self._get( - f"/v1/vector_stores/{vector_store_id}/files/{file_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreFile, - ) - - def list( - self, - vector_store_id: str, - *, - limit: 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, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> FileListResponse: - """ - List files indexed in a vector store with pagination. - - Args: vector_store_id: The ID of the vector store pagination: Pagination - parameters - - Returns: VectorStoreFileListResponse: Paginated list of vector store files - - Args: - vector_store_id: The ID of the vector store - - limit: Maximum number of items to return per page - - offset: Offset of the first item to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return self._get( - f"/v1/vector_stores/{vector_store_id}/files", - 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, - }, - file_list_params.FileListParams, - ), - ), - cast_to=FileListResponse, - ) - - def delete( - self, - file_id: str, - *, - vector_store_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, - ) -> VectorStoreFileDeleted: - """ - Delete a file from a vector store. - - Args: vector_store_id: The ID of the vector store file_id: The ID of the file to - delete - - Returns: VectorStoreFileDeleted: The deleted file - - Args: - vector_store_id: The ID of the vector store - - file_id: The ID of the file to delete - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - if not file_id: - raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") - return self._delete( - f"/v1/vector_stores/{vector_store_id}/files/{file_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreFileDeleted, - ) - - def search( - self, - *, - query: str, - vector_store_ids: List[str], - search_options: file_search_params.SearchOptions | NotGiven = NOT_GIVEN, - top_k: 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, - ) -> FileSearchResponse: - """ - Perform semantic search across complete vector store files. - - This endpoint searches through vector store files using semantic similarity - matching. Unlike chunk search, it returns complete matching files rather than - individual chunks. Supports complex search queries with filters and returns - relevance-scored results. - - Args: search_params: Search configuration including: - query text or - embeddings - metadata filters - pagination parameters - sorting preferences - \\__state: API state dependency \\__ctx: Service context dependency - - Returns: VectorStoreSearchFileResponse containing: - List of matched files with - relevance scores - Pagination details including total result count - - Raises: HTTPException (400): If search parameters are invalid HTTPException - (404): If no vector stores are found to search - - Args: - query: Search query text - - vector_store_ids: IDs of vector stores to search - - search_options: Search configuration options - - top_k: Number of results to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores/files/search", - body=maybe_transform( - { - "query": query, - "vector_store_ids": vector_store_ids, - "search_options": search_options, - "top_k": top_k, - }, - file_search_params.FileSearchParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=FileSearchResponse, - ) - - -class AsyncFilesResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncFilesResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return AsyncFilesResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncFilesResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return AsyncFilesResourceWithStreamingResponse(self) - - async def create( - self, - vector_store_id: str, - *, - file_id: str, - metadata: object | 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, - ) -> VectorStoreFile: - """ - Upload a new file to a vector store for indexing. - - Args: vector_store_id: The ID of the vector store to upload to file: The file to - upload and index - - Returns: VectorStoreFile: Details of the uploaded and indexed file - - Args: - vector_store_id: The ID of the vector store - - file_id: ID of the file to add - - metadata: Optional metadata for the file - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return await self._post( - f"/v1/vector_stores/{vector_store_id}/files", - body=await async_maybe_transform( - { - "file_id": file_id, - "metadata": metadata, - }, - file_create_params.FileCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreFile, - ) - - async def retrieve( - self, - file_id: str, - *, - vector_store_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, - ) -> VectorStoreFile: - """ - Get details of a specific file in a vector store. - - Args: vector_store_id: The ID of the vector store file_id: The ID of the file - - Returns: VectorStoreFile: Details of the vector store file - - Args: - vector_store_id: The ID of the vector store - - file_id: The ID of the file - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - if not file_id: - raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") - return await self._get( - f"/v1/vector_stores/{vector_store_id}/files/{file_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreFile, - ) - - async def list( - self, - vector_store_id: str, - *, - limit: 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, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> FileListResponse: - """ - List files indexed in a vector store with pagination. - - Args: vector_store_id: The ID of the vector store pagination: Pagination - parameters - - Returns: VectorStoreFileListResponse: Paginated list of vector store files - - Args: - vector_store_id: The ID of the vector store - - limit: Maximum number of items to return per page - - offset: Offset of the first item to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return await self._get( - f"/v1/vector_stores/{vector_store_id}/files", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "limit": limit, - "offset": offset, - }, - file_list_params.FileListParams, - ), - ), - cast_to=FileListResponse, - ) - - async def delete( - self, - file_id: str, - *, - vector_store_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, - ) -> VectorStoreFileDeleted: - """ - Delete a file from a vector store. - - Args: vector_store_id: The ID of the vector store file_id: The ID of the file to - delete - - Returns: VectorStoreFileDeleted: The deleted file - - Args: - vector_store_id: The ID of the vector store - - file_id: The ID of the file to delete - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - if not file_id: - raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") - return await self._delete( - f"/v1/vector_stores/{vector_store_id}/files/{file_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreFileDeleted, - ) - - async def search( - self, - *, - query: str, - vector_store_ids: List[str], - search_options: file_search_params.SearchOptions | NotGiven = NOT_GIVEN, - top_k: 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, - ) -> FileSearchResponse: - """ - Perform semantic search across complete vector store files. - - This endpoint searches through vector store files using semantic similarity - matching. Unlike chunk search, it returns complete matching files rather than - individual chunks. Supports complex search queries with filters and returns - relevance-scored results. - - Args: search_params: Search configuration including: - query text or - embeddings - metadata filters - pagination parameters - sorting preferences - \\__state: API state dependency \\__ctx: Service context dependency - - Returns: VectorStoreSearchFileResponse containing: - List of matched files with - relevance scores - Pagination details including total result count - - Raises: HTTPException (400): If search parameters are invalid HTTPException - (404): If no vector stores are found to search - - Args: - query: Search query text - - vector_store_ids: IDs of vector stores to search - - search_options: Search configuration options - - top_k: Number of results to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores/files/search", - body=await async_maybe_transform( - { - "query": query, - "vector_store_ids": vector_store_ids, - "search_options": search_options, - "top_k": top_k, - }, - file_search_params.FileSearchParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=FileSearchResponse, - ) - - -class FilesResourceWithRawResponse: - def __init__(self, files: FilesResource) -> None: - self._files = files - - self.create = to_raw_response_wrapper( - files.create, - ) - self.retrieve = to_raw_response_wrapper( - files.retrieve, - ) - self.list = to_raw_response_wrapper( - files.list, - ) - self.delete = to_raw_response_wrapper( - files.delete, - ) - self.search = to_raw_response_wrapper( - files.search, - ) - - -class AsyncFilesResourceWithRawResponse: - def __init__(self, files: AsyncFilesResource) -> None: - self._files = files - - self.create = async_to_raw_response_wrapper( - files.create, - ) - self.retrieve = async_to_raw_response_wrapper( - files.retrieve, - ) - self.list = async_to_raw_response_wrapper( - files.list, - ) - self.delete = async_to_raw_response_wrapper( - files.delete, - ) - self.search = async_to_raw_response_wrapper( - files.search, - ) - - -class FilesResourceWithStreamingResponse: - def __init__(self, files: FilesResource) -> None: - self._files = files - - self.create = to_streamed_response_wrapper( - files.create, - ) - self.retrieve = to_streamed_response_wrapper( - files.retrieve, - ) - self.list = to_streamed_response_wrapper( - files.list, - ) - self.delete = to_streamed_response_wrapper( - files.delete, - ) - self.search = to_streamed_response_wrapper( - files.search, - ) - - -class AsyncFilesResourceWithStreamingResponse: - def __init__(self, files: AsyncFilesResource) -> None: - self._files = files - - self.create = async_to_streamed_response_wrapper( - files.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - files.retrieve, - ) - self.list = async_to_streamed_response_wrapper( - files.list, - ) - self.delete = async_to_streamed_response_wrapper( - files.delete, - ) - self.search = async_to_streamed_response_wrapper( - files.search, - ) diff --git a/src/mixedbread/resources/vector_stores/vector_stores.py b/src/mixedbread/resources/vector_stores/vector_stores.py deleted file mode 100644 index 94bd80cc..00000000 --- a/src/mixedbread/resources/vector_stores/vector_stores.py +++ /dev/null @@ -1,967 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Optional - -import httpx - -from .files import ( - FilesResource, - AsyncFilesResource, - FilesResourceWithRawResponse, - AsyncFilesResourceWithRawResponse, - FilesResourceWithStreamingResponse, - AsyncFilesResourceWithStreamingResponse, -) -from ...types import ( - vector_store_list_params, - vector_store_create_params, - vector_store_search_params, - vector_store_update_params, - vector_store_question_answering_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.vector_store import VectorStore -from ...types.vector_store_deleted import VectorStoreDeleted -from ...types.vector_store_list_response import VectorStoreListResponse -from ...types.vector_store_search_response import VectorStoreSearchResponse - -__all__ = ["VectorStoresResource", "AsyncVectorStoresResource"] - - -class VectorStoresResource(SyncAPIResource): - @cached_property - def files(self) -> FilesResource: - return FilesResource(self._client) - - @cached_property - def with_raw_response(self) -> VectorStoresResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return VectorStoresResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> VectorStoresResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return VectorStoresResourceWithStreamingResponse(self) - - def create( - self, - *, - description: Optional[str] | NotGiven = NOT_GIVEN, - expires_after: Optional[vector_store_create_params.ExpiresAfter] | NotGiven = NOT_GIVEN, - file_ids: Optional[List[str]] | NotGiven = NOT_GIVEN, - metadata: object | 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, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> VectorStore: - """ - Create a new vector store. - - Args: vector_store_create: VectorStoreCreate object containing the name, - description, and metadata. - - Returns: VectorStore: The response containing the created vector store details. - - Args: - description: Description of the vector store - - expires_after: Represents an expiration policy for a vector store. - - file_ids: Optional list of file IDs - - metadata: Optional metadata key-value pairs - - name: Name for the new vector store - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores", - body=maybe_transform( - { - "description": description, - "expires_after": expires_after, - "file_ids": file_ids, - "metadata": metadata, - "name": name, - }, - vector_store_create_params.VectorStoreCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStore, - ) - - def retrieve( - self, - vector_store_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, - ) -> VectorStore: - """ - Get a vector store by ID. - - Args: vector_store_id: The ID of the vector store to retrieve. - - Returns: VectorStore: The response containing the vector store details. - - Args: - vector_store_id: The ID of the vector store - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return self._get( - f"/v1/vector_stores/{vector_store_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStore, - ) - - def update( - self, - vector_store_id: str, - *, - description: Optional[str] | NotGiven = NOT_GIVEN, - expires_after: Optional[vector_store_update_params.ExpiresAfter] | NotGiven = NOT_GIVEN, - metadata: object | 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, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> VectorStore: - """ - Update a vector store by ID. - - Args: vector_store_id: The ID of the vector store to update. - vector_store_update: VectorStoreCreate object containing the name, description, - and metadata. - - Returns: VectorStore: The response containing the updated vector store details. - - Args: - vector_store_id: The ID of the vector store - - description: New description - - expires_after: Represents an expiration policy for a vector store. - - metadata: Optional metadata key-value pairs - - name: New name for the vector store - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return self._put( - f"/v1/vector_stores/{vector_store_id}", - body=maybe_transform( - { - "description": description, - "expires_after": expires_after, - "metadata": metadata, - "name": name, - }, - vector_store_update_params.VectorStoreUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStore, - ) - - def list( - self, - *, - limit: 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, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> VectorStoreListResponse: - """ - List all vector stores. - - Args: pagination: The pagination options. - - Returns: VectorStoreListResponse: The list of vector stores. - - Args: - limit: Maximum number of items to return per page - - offset: Offset of the first item to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores", - 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, - }, - vector_store_list_params.VectorStoreListParams, - ), - ), - cast_to=VectorStoreListResponse, - ) - - def delete( - self, - vector_store_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, - ) -> VectorStoreDeleted: - """ - Delete a vector store by ID. - - Args: vector_store_id: The ID of the vector store to delete. - - Returns: VectorStore: The response containing the deleted vector store details. - - Args: - vector_store_id: The ID of the vector store to delete - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return self._delete( - f"/v1/vector_stores/{vector_store_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreDeleted, - ) - - def question_answering( - self, - *, - vector_store_ids: List[str], - qa_options: vector_store_question_answering_params.QaOptions | NotGiven = NOT_GIVEN, - query: str | NotGiven = NOT_GIVEN, - search_options: vector_store_question_answering_params.SearchOptions | NotGiven = NOT_GIVEN, - stream: bool | NotGiven = NOT_GIVEN, - top_k: 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: - """ - Question answering - - Args: - vector_store_ids: IDs of vector stores to search - - qa_options: Question answering configuration options - - query: Question to answer. If not provided, the question will be extracted from the - passed messages. - - search_options: Search configuration options - - stream: Whether to stream the answer - - top_k: Number of results to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores/question-answering", - body=maybe_transform( - { - "vector_store_ids": vector_store_ids, - "qa_options": qa_options, - "query": query, - "search_options": search_options, - "stream": stream, - "top_k": top_k, - }, - vector_store_question_answering_params.VectorStoreQuestionAnsweringParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=object, - ) - - def search( - self, - *, - query: str, - vector_store_ids: List[str], - search_options: vector_store_search_params.SearchOptions | NotGiven = NOT_GIVEN, - top_k: 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, - ) -> VectorStoreSearchResponse: - """ - Perform semantic search across vector store chunks. - - This endpoint searches through vector store chunks using semantic similarity - matching. It supports complex search queries with filters and returns - relevance-scored results. - - Args: search_params: Search configuration including: - query text or - embeddings - metadata filters - pagination parameters - sorting preferences - \\__state: API state dependency \\__ctx: Service context dependency - - Returns: VectorStoreSearchChunkResponse containing: - List of matched chunks - with relevance scores - Pagination details including total result count - - Raises: HTTPException (400): If search parameters are invalid HTTPException - (404): If no vector stores are found to search - - Args: - query: Search query text - - vector_store_ids: IDs of vector stores to search - - search_options: Search configuration options - - top_k: Number of results to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores/search", - body=maybe_transform( - { - "query": query, - "vector_store_ids": vector_store_ids, - "search_options": search_options, - "top_k": top_k, - }, - vector_store_search_params.VectorStoreSearchParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreSearchResponse, - ) - - -class AsyncVectorStoresResource(AsyncAPIResource): - @cached_property - def files(self) -> AsyncFilesResource: - return AsyncFilesResource(self._client) - - @cached_property - def with_raw_response(self) -> AsyncVectorStoresResourceWithRawResponse: - """ - 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/mixedbread-python#accessing-raw-response-data-eg-headers - """ - return AsyncVectorStoresResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncVectorStoresResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/mixedbread-python#with_streaming_response - """ - return AsyncVectorStoresResourceWithStreamingResponse(self) - - async def create( - self, - *, - description: Optional[str] | NotGiven = NOT_GIVEN, - expires_after: Optional[vector_store_create_params.ExpiresAfter] | NotGiven = NOT_GIVEN, - file_ids: Optional[List[str]] | NotGiven = NOT_GIVEN, - metadata: object | 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, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> VectorStore: - """ - Create a new vector store. - - Args: vector_store_create: VectorStoreCreate object containing the name, - description, and metadata. - - Returns: VectorStore: The response containing the created vector store details. - - Args: - description: Description of the vector store - - expires_after: Represents an expiration policy for a vector store. - - file_ids: Optional list of file IDs - - metadata: Optional metadata key-value pairs - - name: Name for the new vector store - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores", - body=await async_maybe_transform( - { - "description": description, - "expires_after": expires_after, - "file_ids": file_ids, - "metadata": metadata, - "name": name, - }, - vector_store_create_params.VectorStoreCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStore, - ) - - async def retrieve( - self, - vector_store_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, - ) -> VectorStore: - """ - Get a vector store by ID. - - Args: vector_store_id: The ID of the vector store to retrieve. - - Returns: VectorStore: The response containing the vector store details. - - Args: - vector_store_id: The ID of the vector store - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return await self._get( - f"/v1/vector_stores/{vector_store_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStore, - ) - - async def update( - self, - vector_store_id: str, - *, - description: Optional[str] | NotGiven = NOT_GIVEN, - expires_after: Optional[vector_store_update_params.ExpiresAfter] | NotGiven = NOT_GIVEN, - metadata: object | 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, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> VectorStore: - """ - Update a vector store by ID. - - Args: vector_store_id: The ID of the vector store to update. - vector_store_update: VectorStoreCreate object containing the name, description, - and metadata. - - Returns: VectorStore: The response containing the updated vector store details. - - Args: - vector_store_id: The ID of the vector store - - description: New description - - expires_after: Represents an expiration policy for a vector store. - - metadata: Optional metadata key-value pairs - - name: New name for the vector store - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return await self._put( - f"/v1/vector_stores/{vector_store_id}", - body=await async_maybe_transform( - { - "description": description, - "expires_after": expires_after, - "metadata": metadata, - "name": name, - }, - vector_store_update_params.VectorStoreUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStore, - ) - - async def list( - self, - *, - limit: 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, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> VectorStoreListResponse: - """ - List all vector stores. - - Args: pagination: The pagination options. - - Returns: VectorStoreListResponse: The list of vector stores. - - Args: - limit: Maximum number of items to return per page - - offset: Offset of the first item to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "limit": limit, - "offset": offset, - }, - vector_store_list_params.VectorStoreListParams, - ), - ), - cast_to=VectorStoreListResponse, - ) - - async def delete( - self, - vector_store_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, - ) -> VectorStoreDeleted: - """ - Delete a vector store by ID. - - Args: vector_store_id: The ID of the vector store to delete. - - Returns: VectorStore: The response containing the deleted vector store details. - - Args: - vector_store_id: The ID of the vector store to delete - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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 vector_store_id: - raise ValueError(f"Expected a non-empty value for `vector_store_id` but received {vector_store_id!r}") - return await self._delete( - f"/v1/vector_stores/{vector_store_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreDeleted, - ) - - async def question_answering( - self, - *, - vector_store_ids: List[str], - qa_options: vector_store_question_answering_params.QaOptions | NotGiven = NOT_GIVEN, - query: str | NotGiven = NOT_GIVEN, - search_options: vector_store_question_answering_params.SearchOptions | NotGiven = NOT_GIVEN, - stream: bool | NotGiven = NOT_GIVEN, - top_k: 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: - """ - Question answering - - Args: - vector_store_ids: IDs of vector stores to search - - qa_options: Question answering configuration options - - query: Question to answer. If not provided, the question will be extracted from the - passed messages. - - search_options: Search configuration options - - stream: Whether to stream the answer - - top_k: Number of results to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores/question-answering", - body=await async_maybe_transform( - { - "vector_store_ids": vector_store_ids, - "qa_options": qa_options, - "query": query, - "search_options": search_options, - "stream": stream, - "top_k": top_k, - }, - vector_store_question_answering_params.VectorStoreQuestionAnsweringParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=object, - ) - - async def search( - self, - *, - query: str, - vector_store_ids: List[str], - search_options: vector_store_search_params.SearchOptions | NotGiven = NOT_GIVEN, - top_k: 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, - ) -> VectorStoreSearchResponse: - """ - Perform semantic search across vector store chunks. - - This endpoint searches through vector store chunks using semantic similarity - matching. It supports complex search queries with filters and returns - relevance-scored results. - - Args: search_params: Search configuration including: - query text or - embeddings - metadata filters - pagination parameters - sorting preferences - \\__state: API state dependency \\__ctx: Service context dependency - - Returns: VectorStoreSearchChunkResponse containing: - List of matched chunks - with relevance scores - Pagination details including total result count - - Raises: HTTPException (400): If search parameters are invalid HTTPException - (404): If no vector stores are found to search - - Args: - query: Search query text - - vector_store_ids: IDs of vector stores to search - - search_options: Search configuration options - - top_k: Number of results to return - - extra_headers: Send extra headers - - extra_query: Add additional query parameters 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( - "/v1/vector_stores/search", - body=await async_maybe_transform( - { - "query": query, - "vector_store_ids": vector_store_ids, - "search_options": search_options, - "top_k": top_k, - }, - vector_store_search_params.VectorStoreSearchParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=VectorStoreSearchResponse, - ) - - -class VectorStoresResourceWithRawResponse: - def __init__(self, vector_stores: VectorStoresResource) -> None: - self._vector_stores = vector_stores - - self.create = to_raw_response_wrapper( - vector_stores.create, - ) - self.retrieve = to_raw_response_wrapper( - vector_stores.retrieve, - ) - self.update = to_raw_response_wrapper( - vector_stores.update, - ) - self.list = to_raw_response_wrapper( - vector_stores.list, - ) - self.delete = to_raw_response_wrapper( - vector_stores.delete, - ) - self.question_answering = to_raw_response_wrapper( - vector_stores.question_answering, - ) - self.search = to_raw_response_wrapper( - vector_stores.search, - ) - - @cached_property - def files(self) -> FilesResourceWithRawResponse: - return FilesResourceWithRawResponse(self._vector_stores.files) - - -class AsyncVectorStoresResourceWithRawResponse: - def __init__(self, vector_stores: AsyncVectorStoresResource) -> None: - self._vector_stores = vector_stores - - self.create = async_to_raw_response_wrapper( - vector_stores.create, - ) - self.retrieve = async_to_raw_response_wrapper( - vector_stores.retrieve, - ) - self.update = async_to_raw_response_wrapper( - vector_stores.update, - ) - self.list = async_to_raw_response_wrapper( - vector_stores.list, - ) - self.delete = async_to_raw_response_wrapper( - vector_stores.delete, - ) - self.question_answering = async_to_raw_response_wrapper( - vector_stores.question_answering, - ) - self.search = async_to_raw_response_wrapper( - vector_stores.search, - ) - - @cached_property - def files(self) -> AsyncFilesResourceWithRawResponse: - return AsyncFilesResourceWithRawResponse(self._vector_stores.files) - - -class VectorStoresResourceWithStreamingResponse: - def __init__(self, vector_stores: VectorStoresResource) -> None: - self._vector_stores = vector_stores - - self.create = to_streamed_response_wrapper( - vector_stores.create, - ) - self.retrieve = to_streamed_response_wrapper( - vector_stores.retrieve, - ) - self.update = to_streamed_response_wrapper( - vector_stores.update, - ) - self.list = to_streamed_response_wrapper( - vector_stores.list, - ) - self.delete = to_streamed_response_wrapper( - vector_stores.delete, - ) - self.question_answering = to_streamed_response_wrapper( - vector_stores.question_answering, - ) - self.search = to_streamed_response_wrapper( - vector_stores.search, - ) - - @cached_property - def files(self) -> FilesResourceWithStreamingResponse: - return FilesResourceWithStreamingResponse(self._vector_stores.files) - - -class AsyncVectorStoresResourceWithStreamingResponse: - def __init__(self, vector_stores: AsyncVectorStoresResource) -> None: - self._vector_stores = vector_stores - - self.create = async_to_streamed_response_wrapper( - vector_stores.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - vector_stores.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - vector_stores.update, - ) - self.list = async_to_streamed_response_wrapper( - vector_stores.list, - ) - self.delete = async_to_streamed_response_wrapper( - vector_stores.delete, - ) - self.question_answering = async_to_streamed_response_wrapper( - vector_stores.question_answering, - ) - self.search = async_to_streamed_response_wrapper( - vector_stores.search, - ) - - @cached_property - def files(self) -> AsyncFilesResourceWithStreamingResponse: - return AsyncFilesResourceWithStreamingResponse(self._vector_stores.files) diff --git a/src/mixedbread/types/__init__.py b/src/mixedbread/types/__init__.py index 7afb0f50..306c151e 100644 --- a/src/mixedbread/types/__init__.py +++ b/src/mixedbread/types/__init__.py @@ -2,25 +2,63 @@ from __future__ import annotations +from . import shared +from .. import _compat +from .store import Store as Store +from .shared import Usage as Usage, SearchFilter as SearchFilter, SearchFilterCondition as SearchFilterCondition +from .api_key import APIKey as APIKey +from .embedding import Embedding as Embedding +from .data_source import DataSource as DataSource from .file_object import FileObject as FileObject -from .file_deleted import FileDeleted as FileDeleted -from .vector_store import VectorStore as VectorStore +from .expires_after import ExpiresAfter as ExpiresAfter from .info_response import InfoResponse as InfoResponse +from .oauth2_params import Oauth2Params as Oauth2Params +from .api_key_created import APIKeyCreated as APIKeyCreated +from .encoding_format import EncodingFormat as EncodingFormat +from .rerank_response import RerankResponse as RerankResponse +from .data_source_type import DataSourceType as DataSourceType from .file_list_params import FileListParams as FileListParams +from .store_list_params import StoreListParams as StoreListParams from .file_create_params import FileCreateParams as FileCreateParams -from .file_list_response import FileListResponse as FileListResponse from .file_update_params import FileUpdateParams as FileUpdateParams -from .vector_store_deleted import VectorStoreDeleted as VectorStoreDeleted +from .api_key_list_params import APIKeyListParams as APIKeyListParams +from .client_embed_params import ClientEmbedParams as ClientEmbedParams +from .expires_after_param import ExpiresAfterParam as ExpiresAfterParam +from .store_create_params import StoreCreateParams as StoreCreateParams +from .store_search_params import StoreSearchParams as StoreSearchParams +from .store_update_params import StoreUpdateParams as StoreUpdateParams +from .client_rerank_params import ClientRerankParams as ClientRerankParams +from .file_delete_response import FileDeleteResponse as FileDeleteResponse +from .api_key_create_params import APIKeyCreateParams as APIKeyCreateParams +from .pagination_with_total import PaginationWithTotal as PaginationWithTotal +from .store_delete_response import StoreDeleteResponse as StoreDeleteResponse +from .store_search_response import StoreSearchResponse as StoreSearchResponse +from .api_key_delete_response import APIKeyDeleteResponse as APIKeyDeleteResponse +from .data_source_list_params import DataSourceListParams as DataSourceListParams from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams -from .reranking_create_params import RerankingCreateParams as RerankingCreateParams -from .vector_store_list_params import VectorStoreListParams as VectorStoreListParams +from .scored_text_input_chunk import ScoredTextInputChunk as ScoredTextInputChunk +from .linear_data_source_param import LinearDataSourceParam as LinearDataSourceParam +from .multi_encoding_embedding import MultiEncodingEmbedding as MultiEncodingEmbedding +from .notion_data_source_param import NotionDataSourceParam as NotionDataSourceParam +from .data_source_create_params import DataSourceCreateParams as DataSourceCreateParams +from .data_source_oauth2_params import DataSourceOauth2Params as DataSourceOauth2Params +from .data_source_update_params import DataSourceUpdateParams as DataSourceUpdateParams from .embedding_create_response import EmbeddingCreateResponse as EmbeddingCreateResponse -from .reranking_create_response import RerankingCreateResponse as RerankingCreateResponse -from .vector_store_create_params import VectorStoreCreateParams as VectorStoreCreateParams -from .vector_store_list_response import VectorStoreListResponse as VectorStoreListResponse -from .vector_store_search_params import VectorStoreSearchParams as VectorStoreSearchParams -from .vector_store_update_params import VectorStoreUpdateParams as VectorStoreUpdateParams -from .vector_store_search_response import VectorStoreSearchResponse as VectorStoreSearchResponse -from .vector_store_question_answering_params import ( - VectorStoreQuestionAnsweringParams as VectorStoreQuestionAnsweringParams, -) +from .data_source_delete_response import DataSourceDeleteResponse as DataSourceDeleteResponse +from .scored_audio_url_input_chunk import ScoredAudioURLInputChunk as ScoredAudioURLInputChunk +from .scored_image_url_input_chunk import ScoredImageURLInputChunk as ScoredImageURLInputChunk +from .scored_video_url_input_chunk import ScoredVideoURLInputChunk as ScoredVideoURLInputChunk +from .store_metadata_facets_params import StoreMetadataFacetsParams as StoreMetadataFacetsParams +from .store_metadata_facets_response import StoreMetadataFacetsResponse as StoreMetadataFacetsResponse +from .store_question_answering_params import StoreQuestionAnsweringParams as StoreQuestionAnsweringParams +from .store_chunk_search_options_param import StoreChunkSearchOptionsParam as StoreChunkSearchOptionsParam +from .store_question_answering_response import StoreQuestionAnsweringResponse as StoreQuestionAnsweringResponse + +# Rebuild cyclical models only after all modules are imported. +# This ensures that, when building the deferred (due to cyclical references) model schema, +# Pydantic can resolve the necessary references. +# See: https://github.com/pydantic/pydantic/issues/11250 for more context. +if _compat.PYDANTIC_V1: + shared.search_filter.SearchFilter.update_forward_refs() # type: ignore +else: + shared.search_filter.SearchFilter.model_rebuild(_parent_namespace_depth=0) diff --git a/src/mixedbread/types/api_key.py b/src/mixedbread/types/api_key.py new file mode 100644 index 00000000..ab7b375d --- /dev/null +++ b/src/mixedbread/types/api_key.py @@ -0,0 +1,48 @@ +# 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__ = ["APIKey", "Scope"] + + +class Scope(BaseModel): + method: Literal["read", "write", "delete", "list", "create", "search"] + + resource_type: Optional[Literal["store"]] = None + + resource_id: Optional[str] = None + + +class APIKey(BaseModel): + """Response model for an API key.""" + + id: str + """The ID of the API key""" + + name: str + """The name of the API key""" + + redacted_value: str + """The redacted value of the API key""" + + expires_at: Optional[datetime] = None + """The expiration datetime of the API key""" + + created_at: datetime + """The creation datetime of the API key""" + + updated_at: datetime + """The last update datetime of the API key""" + + last_active_at: Optional[datetime] = None + """The last active datetime of the API key""" + + object: Optional[Literal["api_key"]] = None + """The type of the object""" + + scope: Optional[List[Scope]] = None + """The scope of the API key""" diff --git a/src/mixedbread/types/api_key_create_params.py b/src/mixedbread/types/api_key_create_params.py new file mode 100644 index 00000000..829cd517 --- /dev/null +++ b/src/mixedbread/types/api_key_create_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["APIKeyCreateParams", "Scope"] + + +class APIKeyCreateParams(TypedDict, total=False): + name: str + """A name/description for the API key""" + + scope: Optional[Iterable[Scope]] + """The scope of the API key""" + + expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Optional expiration datetime""" + + +class Scope(TypedDict, total=False): + method: Required[Literal["read", "write", "delete", "list", "create", "search"]] + + resource_type: Optional[Literal["store"]] + + resource_id: Optional[str] diff --git a/src/mixedbread/types/api_key_created.py b/src/mixedbread/types/api_key_created.py new file mode 100644 index 00000000..3efdb33a --- /dev/null +++ b/src/mixedbread/types/api_key_created.py @@ -0,0 +1,51 @@ +# 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__ = ["APIKeyCreated", "Scope"] + + +class Scope(BaseModel): + method: Literal["read", "write", "delete", "list", "create", "search"] + + resource_type: Optional[Literal["store"]] = None + + resource_id: Optional[str] = None + + +class APIKeyCreated(BaseModel): + """Response model for creating an API key.""" + + id: str + """The ID of the API key""" + + name: str + """The name of the API key""" + + redacted_value: str + """The redacted value of the API key""" + + expires_at: Optional[datetime] = None + """The expiration datetime of the API key""" + + created_at: datetime + """The creation datetime of the API key""" + + updated_at: datetime + """The last update datetime of the API key""" + + last_active_at: Optional[datetime] = None + """The last active datetime of the API key""" + + object: Optional[Literal["api_key"]] = None + """The type of the object""" + + scope: Optional[List[Scope]] = None + """The scope of the API key""" + + value: str + """The value of the API key""" diff --git a/src/mixedbread/types/api_key_delete_response.py b/src/mixedbread/types/api_key_delete_response.py new file mode 100644 index 00000000..31e87669 --- /dev/null +++ b/src/mixedbread/types/api_key_delete_response.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["APIKeyDeleteResponse"] + + +class APIKeyDeleteResponse(BaseModel): + """Response model for deleting an API key.""" + + id: str + """The ID of the deleted API key""" + + deleted: bool + """Whether the API key was deleted""" + + object: Optional[Literal["api_key"]] = None + """The type of the object deleted""" diff --git a/src/mixedbread/types/vector_stores/file_list_params.py b/src/mixedbread/types/api_key_list_params.py similarity index 78% rename from src/mixedbread/types/vector_stores/file_list_params.py rename to src/mixedbread/types/api_key_list_params.py index 7ecfdf54..748045a4 100644 --- a/src/mixedbread/types/vector_stores/file_list_params.py +++ b/src/mixedbread/types/api_key_list_params.py @@ -4,10 +4,10 @@ from typing_extensions import TypedDict -__all__ = ["FileListParams"] +__all__ = ["APIKeyListParams"] -class FileListParams(TypedDict, total=False): +class APIKeyListParams(TypedDict, total=False): limit: int """Maximum number of items to return per page""" diff --git a/src/mixedbread/types/client_embed_params.py b/src/mixedbread/types/client_embed_params.py new file mode 100644 index 00000000..7a5080bc --- /dev/null +++ b/src/mixedbread/types/client_embed_params.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from typing_extensions import Required, TypedDict + +from .._types import SequenceNotStr +from .encoding_format import EncodingFormat + +__all__ = ["ClientEmbedParams"] + + +class ClientEmbedParams(TypedDict, total=False): + model: Required[str] + """The model to use for creating embeddings.""" + + input: Required[Union[str, SequenceNotStr[str]]] + """The input to create embeddings for.""" + + dimensions: Optional[int] + """The number of dimensions to use for the embeddings.""" + + prompt: Optional[str] + """The prompt to use for the embedding creation.""" + + normalized: bool + """Whether to normalize the embeddings.""" + + encoding_format: Union[EncodingFormat, List[EncodingFormat]] + """The encoding format(s) of the embeddings. + + Can be a single format or a list of formats. + """ diff --git a/src/mixedbread/types/reranking_create_params.py b/src/mixedbread/types/client_rerank_params.py similarity index 57% rename from src/mixedbread/types/reranking_create_params.py rename to src/mixedbread/types/client_rerank_params.py index cbf9514e..ee628e54 100644 --- a/src/mixedbread/types/reranking_create_params.py +++ b/src/mixedbread/types/client_rerank_params.py @@ -2,27 +2,32 @@ from __future__ import annotations -from typing import List, Union, Optional +from typing import Union, Iterable, Optional from typing_extensions import Required, TypedDict -__all__ = ["RerankingCreateParams"] +from .._types import SequenceNotStr +__all__ = ["ClientRerankParams"] -class RerankingCreateParams(TypedDict, total=False): - input: Required[List[Union[str, object]]] - """The input documents to rerank.""" + +class ClientRerankParams(TypedDict, total=False): + model: str + """The model to use for reranking documents.""" query: Required[str] """The query to rerank the documents.""" - model: str - """The model to use for reranking documents.""" + input: Required[SequenceNotStr[Union[str, Iterable[object], object]]] + """The input documents to rerank.""" - rank_fields: Optional[List[str]] + rank_fields: Optional[SequenceNotStr[str]] """The fields of the documents to rank.""" + top_k: int + """The number of documents to return.""" + return_input: bool """Whether to return the documents.""" - top_k: int - """The number of documents to return.""" + rewrite_query: bool + """Wether or not to rewrite the query before passing it to the reranking model""" diff --git a/src/mixedbread/types/data_source.py b/src/mixedbread/types/data_source.py new file mode 100644 index 00000000..d219ef28 --- /dev/null +++ b/src/mixedbread/types/data_source.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .data_source_type import DataSourceType +from .data_source_oauth2_params import DataSourceOauth2Params + +__all__ = ["DataSource", "AuthParams", "AuthParamsDataSourceAPIKeyParams"] + + +class AuthParamsDataSourceAPIKeyParams(BaseModel): + """Authentication parameters for a API key data source.""" + + type: Optional[Literal["api_key"]] = None + + api_key: str + """The API key""" + + +AuthParams: TypeAlias = Annotated[ + Union[DataSourceOauth2Params, AuthParamsDataSourceAPIKeyParams, None], PropertyInfo(discriminator="type") +] + + +class DataSource(BaseModel): + """Service-level representation of a data source.""" + + id: str + """The ID of the data source""" + + created_at: datetime + """The creation time of the data source""" + + updated_at: datetime + """The last update time of the data source""" + + type: DataSourceType + """The type of data source""" + + name: str + """The name of the data source""" + + metadata: object + """The metadata of the data source""" + + auth_params: Optional[AuthParams] = None + """Authentication parameters""" + + object: Optional[Literal["data_source"]] = None + """The type of the object""" diff --git a/src/mixedbread/types/data_source_create_params.py b/src/mixedbread/types/data_source_create_params.py new file mode 100644 index 00000000..9424a1de --- /dev/null +++ b/src/mixedbread/types/data_source_create_params.py @@ -0,0 +1,62 @@ +# 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, TypeAlias, TypedDict + +from .oauth2_params import Oauth2Params + +__all__ = [ + "DataSourceCreateParams", + "NotionDataSource", + "NotionDataSourceAuthParams", + "NotionDataSourceAuthParamsAPIKeyCreateOrUpdateParams", + "LinearDataSource", +] + + +class NotionDataSource(TypedDict, total=False): + type: Literal["notion"] + """The type of data source to create""" + + name: Required[str] + """The name of the data source""" + + metadata: object + """The metadata of the data source""" + + auth_params: Optional[NotionDataSourceAuthParams] + """The authentication parameters of the data source. + + Notion supports OAuth2 and API key. + """ + + +class NotionDataSourceAuthParamsAPIKeyCreateOrUpdateParams(TypedDict, total=False): + """Base class for API key create or update parameters.""" + + type: Literal["api_key"] + + api_key: Required[str] + """The API key""" + + +NotionDataSourceAuthParams: TypeAlias = Union[Oauth2Params, NotionDataSourceAuthParamsAPIKeyCreateOrUpdateParams] + + +class LinearDataSource(TypedDict, total=False): + type: Literal["linear"] + """The type of data source to create""" + + name: Required[str] + """The name of the data source""" + + metadata: object + """The metadata of the data source""" + + auth_params: Optional[Oauth2Params] + """Base class for OAuth2 create or update parameters.""" + + +DataSourceCreateParams: TypeAlias = Union[NotionDataSource, LinearDataSource] diff --git a/src/mixedbread/types/data_source_delete_response.py b/src/mixedbread/types/data_source_delete_response.py new file mode 100644 index 00000000..98a38e0c --- /dev/null +++ b/src/mixedbread/types/data_source_delete_response.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DataSourceDeleteResponse"] + + +class DataSourceDeleteResponse(BaseModel): + """Deleted data source.""" + + id: str + """The ID of the data source""" + + deleted: Optional[bool] = None + """Whether the data source was deleted""" + + object: Optional[Literal["data_source"]] = None + """The type of the object""" diff --git a/src/mixedbread/types/data_source_list_params.py b/src/mixedbread/types/data_source_list_params.py new file mode 100644 index 00000000..07eb80f4 --- /dev/null +++ b/src/mixedbread/types/data_source_list_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["DataSourceListParams"] + + +class DataSourceListParams(TypedDict, total=False): + limit: int + """Maximum number of items to return per page (1-100)""" + + after: Optional[str] + """Cursor for forward pagination - get items after this position. + + Use last_cursor from previous response. + """ + + before: Optional[str] + """Cursor for backward pagination - get items before this position. + + Use first_cursor from previous response. + """ + + include_total: bool + """Whether to include total count in response (expensive operation)""" diff --git a/src/mixedbread/types/data_source_oauth2_params.py b/src/mixedbread/types/data_source_oauth2_params.py new file mode 100644 index 00000000..b19f4a83 --- /dev/null +++ b/src/mixedbread/types/data_source_oauth2_params.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DataSourceOauth2Params"] + + +class DataSourceOauth2Params(BaseModel): + """Authentication parameters for a OAuth data source.""" + + type: Optional[Literal["oauth2"]] = None + + created_at: Optional[datetime] = None + """The timestamp when the OAuth2 credentials were created""" + + scope: Optional[str] = None + """The OAuth2 scope""" + + access_token: Optional[str] = None + """The OAuth2 access token""" + + refresh_token: Optional[str] = None + """The OAuth2 refresh token""" + + token_type: Optional[str] = None + """The OAuth2 token type""" + + expires_on: Optional[datetime] = None + """The OAuth2 token expiration timestamp""" + + additional_params: Optional[Dict[str, object]] = None + """Additional parameters for the OAuth2 flow""" diff --git a/src/mixedbread/types/data_source_type.py b/src/mixedbread/types/data_source_type.py new file mode 100644 index 00000000..0851808b --- /dev/null +++ b/src/mixedbread/types/data_source_type.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["DataSourceType"] + +DataSourceType: TypeAlias = Literal["notion", "linear"] diff --git a/src/mixedbread/types/data_source_update_params.py b/src/mixedbread/types/data_source_update_params.py new file mode 100644 index 00000000..96880db4 --- /dev/null +++ b/src/mixedbread/types/data_source_update_params.py @@ -0,0 +1,62 @@ +# 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, TypeAlias, TypedDict + +from .oauth2_params import Oauth2Params + +__all__ = [ + "DataSourceUpdateParams", + "NotionDataSource", + "NotionDataSourceAuthParams", + "NotionDataSourceAuthParamsAPIKeyCreateOrUpdateParams", + "LinearDataSource", +] + + +class NotionDataSource(TypedDict, total=False): + type: Literal["notion"] + """The type of data source to create""" + + name: Required[str] + """The name of the data source""" + + metadata: object + """The metadata of the data source""" + + auth_params: Optional[NotionDataSourceAuthParams] + """The authentication parameters of the data source. + + Notion supports OAuth2 and API key. + """ + + +class NotionDataSourceAuthParamsAPIKeyCreateOrUpdateParams(TypedDict, total=False): + """Base class for API key create or update parameters.""" + + type: Literal["api_key"] + + api_key: Required[str] + """The API key""" + + +NotionDataSourceAuthParams: TypeAlias = Union[Oauth2Params, NotionDataSourceAuthParamsAPIKeyCreateOrUpdateParams] + + +class LinearDataSource(TypedDict, total=False): + type: Literal["linear"] + """The type of data source to create""" + + name: Required[str] + """The name of the data source""" + + metadata: object + """The metadata of the data source""" + + auth_params: Optional[Oauth2Params] + """Base class for OAuth2 create or update parameters.""" + + +DataSourceUpdateParams: TypeAlias = Union[NotionDataSource, LinearDataSource] diff --git a/src/mixedbread/types/data_sources/__init__.py b/src/mixedbread/types/data_sources/__init__.py new file mode 100644 index 00000000..c35b6f2e --- /dev/null +++ b/src/mixedbread/types/data_sources/__init__.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .connector_list_params import ConnectorListParams as ConnectorListParams +from .data_source_connector import DataSourceConnector as DataSourceConnector +from .connector_create_params import ConnectorCreateParams as ConnectorCreateParams +from .connector_update_params import ConnectorUpdateParams as ConnectorUpdateParams +from .connector_delete_response import ConnectorDeleteResponse as ConnectorDeleteResponse diff --git a/src/mixedbread/types/data_sources/connector_create_params.py b/src/mixedbread/types/data_sources/connector_create_params.py new file mode 100644 index 00000000..185c3894 --- /dev/null +++ b/src/mixedbread/types/data_sources/connector_create_params.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Required, TypedDict + +__all__ = ["ConnectorCreateParams"] + + +class ConnectorCreateParams(TypedDict, total=False): + store_id: Required[str] + """The ID of the store""" + + name: str + """The name of the connector""" + + trigger_sync: bool + """Whether the connector should be synced after creation""" + + metadata: object + """The metadata of the connector""" + + polling_interval: Union[int, str, None] + """Polling interval for the connector. + + Defaults to 30 minutes if not specified. Can be provided as: + + - int: Number of seconds (e.g., 1800 for 30 minutes) + - str: Duration string (e.g., '30m', '1h', '2d') or ISO 8601 format (e.g., + 'PT30M', 'P1D') Valid range: 15 seconds to 30 days + """ diff --git a/src/mixedbread/types/data_sources/connector_delete_response.py b/src/mixedbread/types/data_sources/connector_delete_response.py new file mode 100644 index 00000000..079ccf7f --- /dev/null +++ b/src/mixedbread/types/data_sources/connector_delete_response.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ConnectorDeleteResponse"] + + +class ConnectorDeleteResponse(BaseModel): + """Deleted connector.""" + + id: str + """The ID of the connector""" + + deleted: Optional[bool] = None + """Whether the connector was deleted""" + + object: Optional[Literal["data_source.connector"]] = None + """The type of the object""" diff --git a/src/mixedbread/types/data_sources/connector_list_params.py b/src/mixedbread/types/data_sources/connector_list_params.py new file mode 100644 index 00000000..cb3da959 --- /dev/null +++ b/src/mixedbread/types/data_sources/connector_list_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["ConnectorListParams"] + + +class ConnectorListParams(TypedDict, total=False): + limit: int + """Maximum number of items to return per page (1-100)""" + + after: Optional[str] + """Cursor for forward pagination - get items after this position. + + Use last_cursor from previous response. + """ + + before: Optional[str] + """Cursor for backward pagination - get items before this position. + + Use first_cursor from previous response. + """ + + include_total: bool + """Whether to include total count in response (expensive operation)""" diff --git a/src/mixedbread/types/data_sources/connector_update_params.py b/src/mixedbread/types/data_sources/connector_update_params.py new file mode 100644 index 00000000..7e0d112b --- /dev/null +++ b/src/mixedbread/types/data_sources/connector_update_params.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ConnectorUpdateParams"] + + +class ConnectorUpdateParams(TypedDict, total=False): + data_source_id: Required[str] + """The ID of the data source to update a connector for""" + + name: Optional[str] + """The name of the connector""" + + metadata: Optional[Dict[str, object]] + """The metadata of the connector""" + + trigger_sync: Optional[bool] + """Whether the connector should be synced after update""" + + polling_interval: Union[int, str, None] + """Polling interval for the connector. + + Defaults to 30 minutes if not specified. Can be provided as: + + - int: Number of seconds (e.g., 1800 for 30 minutes) + - str: Duration string (e.g., '30m', '1h', '2d') or ISO 8601 format (e.g., + 'PT30M', 'P1D') Valid range: 15 seconds to 30 days + """ diff --git a/src/mixedbread/types/data_sources/data_source_connector.py b/src/mixedbread/types/data_sources/data_source_connector.py new file mode 100644 index 00000000..0fc36723 --- /dev/null +++ b/src/mixedbread/types/data_sources/data_source_connector.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["DataSourceConnector"] + + +class DataSourceConnector(BaseModel): + """Service-level representation of a connector.""" + + id: str + """The ID of the connector""" + + created_at: datetime + """The creation time of the connector""" + + updated_at: datetime + """The last update time of the connector""" + + store_id: str + """The ID of the store""" + + data_source_id: str + """The ID of the data source""" + + name: Optional[str] = None + """The name of the connector""" + + metadata: object + """The metadata of the connector""" + + polling_interval: str + """The polling interval of the connector""" + + started_at: Optional[datetime] = None + """The start time of the connector""" + + finished_at: Optional[datetime] = None + """The finish time of the connector""" + + last_synced_at: Optional[datetime] = None + """The last sync time of the connector""" + + status: Literal["idle", "pending", "in_progress", "cancelled", "completed", "failed"] + """The sync status of the connector""" + + error: Optional[Dict[str, object]] = None + """The sync error of the connector""" + + object: Optional[Literal["data_source.connector"]] = None + """The type of the object""" diff --git a/src/mixedbread/types/embedding.py b/src/mixedbread/types/embedding.py new file mode 100644 index 00000000..19fd8136 --- /dev/null +++ b/src/mixedbread/types/embedding.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["Embedding"] + + +class Embedding(BaseModel): + embedding: Union[List[float], List[int], str] + """The encoded embedding.""" + + index: int + """The index of the embedding.""" + + object: Optional[Literal["embedding"]] = None + """The object type of the embedding.""" diff --git a/src/mixedbread/types/embedding_create_params.py b/src/mixedbread/types/embedding_create_params.py index 6ca77dcf..ab848f77 100644 --- a/src/mixedbread/types/embedding_create_params.py +++ b/src/mixedbread/types/embedding_create_params.py @@ -3,53 +3,32 @@ from __future__ import annotations from typing import List, Union, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing_extensions import Required, TypedDict -__all__ = ["EmbeddingCreateParams", "Input", "InputImageURLInput", "InputImageURLInputImage", "InputTextInput"] +from .._types import SequenceNotStr +from .encoding_format import EncodingFormat +__all__ = ["EmbeddingCreateParams"] -class EmbeddingCreateParams(TypedDict, total=False): - input: Required[Input] - """The input to create embeddings for.""" +class EmbeddingCreateParams(TypedDict, total=False): model: Required[str] """The model to use for creating embeddings.""" + input: Required[Union[str, SequenceNotStr[str]]] + """The input to create embeddings for.""" + dimensions: Optional[int] """The number of dimensions to use for the embeddings.""" - encoding_format: Union[ - Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"], - List[Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"]], - ] - """The encoding format of the embeddings.""" - - normalized: bool - """Whether to normalize the embeddings.""" - prompt: Optional[str] """The prompt to use for the embedding creation.""" + normalized: bool + """Whether to normalize the embeddings.""" -class InputImageURLInputImage(TypedDict, total=False): - url: Required[str] - """The image URL. Can be either a URL or a Data URI.""" - - -class InputImageURLInput(TypedDict, total=False): - image: Required[InputImageURLInputImage] - """The image input specification.""" - - type: Literal["image_url"] - """Input type identifier""" - - -class InputTextInput(TypedDict, total=False): - text: Required[str] - """Text content to process""" - - type: Literal["text"] - """Input type identifier""" - + encoding_format: Union[EncodingFormat, List[EncodingFormat]] + """The encoding format(s) of the embeddings. -Input: TypeAlias = Union[str, InputImageURLInput, InputTextInput] + Can be a single format or a list of formats. + """ diff --git a/src/mixedbread/types/embedding_create_response.py b/src/mixedbread/types/embedding_create_response.py index ffc8eff2..ba5adbf6 100644 --- a/src/mixedbread/types/embedding_create_response.py +++ b/src/mixedbread/types/embedding_create_response.py @@ -1,97 +1,35 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import builtins from typing import List, Union, Optional from typing_extensions import Literal from .._models import BaseModel +from .embedding import Embedding +from .shared.usage import Usage +from .encoding_format import EncodingFormat +from .multi_encoding_embedding import MultiEncodingEmbedding -__all__ = ["EmbeddingCreateResponse", "DataUnionMember0", "DataUnionMember1", "DataUnionMember1Embedding", "Usage"] - - -class DataUnionMember0(BaseModel): - embedding: Union[List[float], List[int], str] - """The encoded embedding.""" - - index: int - """The index of the embedding.""" - - object: Literal["embedding"] - """The object type of the embedding.""" - - -class DataUnionMember1Embedding(BaseModel): - base64: Optional[List[str]] = None - - binary: Optional[List[int]] = None - - float: Optional[List[builtins.float]] = None - - int8: Optional[List[int]] = None - - ubinary: Optional[List[int]] = None - - uint8: Optional[List[int]] = None - - -class DataUnionMember1(BaseModel): - embedding: DataUnionMember1Embedding - """ - The encoded embedding data by encoding format.Returned, if more than one - encoding format is used. - """ - - index: int - """The index of the embedding.""" - - object: Literal["embedding_dict"] - """The object type of the embedding.""" - - -class Usage(BaseModel): - prompt_tokens: int - """The number of tokens used for the prompt""" - - total_tokens: int - """The total number of tokens used""" - - completion_tokens: Optional[int] = None - """The number of tokens used for the completion""" +__all__ = ["EmbeddingCreateResponse"] class EmbeddingCreateResponse(BaseModel): - data: Union[List[DataUnionMember0], List[DataUnionMember1]] - """The created embeddings.""" - - dimensions: Optional[int] = None - """The number of dimensions used for the embeddings.""" - - encoding_format: Union[ - Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"], - List[Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"]], - ] - """The encoding format of the embeddings.""" + usage: Usage + """The usage of the model""" model: str """The model used""" + data: Union[List[Embedding], List[MultiEncodingEmbedding]] + """The created embeddings.""" + + object: Optional[Literal["list"]] = None + """The object type of the response""" + normalized: bool """Whether the embeddings are normalized.""" - usage: Usage - """The usage of the model""" + encoding_format: Union[EncodingFormat, List[EncodingFormat]] + """The encoding formats of the embeddings.""" - object: Optional[ - Literal[ - "list", - "job", - "embedding", - "embedding_dict", - "text_document", - "file", - "vector_store", - "vector_store.file", - "api_key", - ] - ] = None - """The object type of the response""" + dimensions: Optional[int] = None + """The number of dimensions used for the embeddings.""" diff --git a/src/mixedbread/types/encoding_format.py b/src/mixedbread/types/encoding_format.py new file mode 100644 index 00000000..74960945 --- /dev/null +++ b/src/mixedbread/types/encoding_format.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["EncodingFormat"] + +EncodingFormat: TypeAlias = Literal["float", "float16", "base64", "binary", "ubinary", "int8", "uint8"] diff --git a/src/mixedbread/types/expires_after.py b/src/mixedbread/types/expires_after.py new file mode 100644 index 00000000..4c3843e1 --- /dev/null +++ b/src/mixedbread/types/expires_after.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ExpiresAfter"] + + +class ExpiresAfter(BaseModel): + """Represents an expiration policy for a store.""" + + anchor: Optional[Literal["last_active_at"]] = None + """Anchor date for the expiration policy""" + + days: Optional[int] = None + """Number of days after which the store expires""" diff --git a/src/mixedbread/types/expires_after_param.py b/src/mixedbread/types/expires_after_param.py new file mode 100644 index 00000000..1715aacf --- /dev/null +++ b/src/mixedbread/types/expires_after_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["ExpiresAfterParam"] + + +class ExpiresAfterParam(TypedDict, total=False): + """Represents an expiration policy for a store.""" + + anchor: Literal["last_active_at"] + """Anchor date for the expiration policy""" + + days: int + """Number of days after which the store expires""" diff --git a/src/mixedbread/types/extractions/__init__.py b/src/mixedbread/types/extractions/__init__.py index 6457c670..ca9fd1eb 100644 --- a/src/mixedbread/types/extractions/__init__.py +++ b/src/mixedbread/types/extractions/__init__.py @@ -3,12 +3,14 @@ from __future__ import annotations from .extraction_job import ExtractionJob as ExtractionJob +from .text_input_param import TextInputParam as TextInputParam from .extraction_result import ExtractionResult as ExtractionResult from .job_create_params import JobCreateParams as JobCreateParams from .created_json_schema import CreatedJsonSchema as CreatedJsonSchema from .enhanced_json_schema import EnhancedJsonSchema as EnhancedJsonSchema from .schema_create_params import SchemaCreateParams as SchemaCreateParams from .content_create_params import ContentCreateParams as ContentCreateParams +from .image_url_input_param import ImageURLInputParam as ImageURLInputParam from .schema_enhance_params import SchemaEnhanceParams as SchemaEnhanceParams from .validated_json_schema import ValidatedJsonSchema as ValidatedJsonSchema from .schema_validate_params import SchemaValidateParams as SchemaValidateParams diff --git a/src/mixedbread/types/extractions/content_create_params.py b/src/mixedbread/types/extractions/content_create_params.py index 2bbdada2..54faba84 100644 --- a/src/mixedbread/types/extractions/content_create_params.py +++ b/src/mixedbread/types/extractions/content_create_params.py @@ -2,14 +2,25 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Required, TypeAlias, TypedDict -__all__ = ["ContentCreateParams"] +from ..._types import SequenceNotStr +from .text_input_param import TextInputParam +from .image_url_input_param import ImageURLInputParam + +__all__ = ["ContentCreateParams", "ContentUnionMember2"] class ContentCreateParams(TypedDict, total=False): - content: Required[str] + content: Required[Union[str, SequenceNotStr[str], Iterable[ContentUnionMember2]]] """The content to extract from""" - json_schema: Required[object] + json_schema: Required[Dict[str, object]] """The JSON schema to use for extraction""" + + instructions: Optional[str] + """Additional instructions for the extraction""" + + +ContentUnionMember2: TypeAlias = Union[TextInputParam, ImageURLInputParam] diff --git a/src/mixedbread/types/extractions/created_json_schema.py b/src/mixedbread/types/extractions/created_json_schema.py index 4b50c4c6..dec87e18 100644 --- a/src/mixedbread/types/extractions/created_json_schema.py +++ b/src/mixedbread/types/extractions/created_json_schema.py @@ -1,5 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Dict from ..._models import BaseModel @@ -7,5 +8,7 @@ class CreatedJsonSchema(BaseModel): - json_schema: object + """Result of creating a JSON schema.""" + + json_schema: Dict[str, object] """The created JSON schema""" diff --git a/src/mixedbread/types/extractions/enhanced_json_schema.py b/src/mixedbread/types/extractions/enhanced_json_schema.py index 7b2ab04a..2e789cce 100644 --- a/src/mixedbread/types/extractions/enhanced_json_schema.py +++ b/src/mixedbread/types/extractions/enhanced_json_schema.py @@ -1,5 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Dict from ..._models import BaseModel @@ -7,5 +8,7 @@ class EnhancedJsonSchema(BaseModel): - json_schema: object + """Result of enhancing a JSON schema.""" + + json_schema: Dict[str, object] """The enhanced JSON schema""" diff --git a/src/mixedbread/types/extractions/extraction_job.py b/src/mixedbread/types/extractions/extraction_job.py index d6e204c9..b3bececf 100644 --- a/src/mixedbread/types/extractions/extraction_job.py +++ b/src/mixedbread/types/extractions/extraction_job.py @@ -1,33 +1,47 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, Optional from datetime import datetime -from typing_extensions import Literal from ..._models import BaseModel from .extraction_result import ExtractionResult +from ..parsing.parsing_job_status import ParsingJobStatus __all__ = ["ExtractionJob"] class ExtractionJob(BaseModel): + """A job for extracting structured data from documents.""" + id: str - """The ID of the job""" + """Unique identifier for the extraction job""" - result: Optional[ExtractionResult] = None - """Result of an extraction operation.""" + organization_id: str + """ID of the organization that owns this job""" - status: Literal["none", "running", "canceled", "successful", "failed", "resumable", "pending"] - """The status of the job""" + file_id: str + """ID of the file being extracted""" - created_at: Optional[datetime] = None - """The creation time of the job""" + created_at: datetime + """When the job was created""" - errors: Optional[List[str]] = None - """The errors of the job""" + updated_at: datetime + """When the job was last updated""" + + started_at: Optional[datetime] = None + """When the job started processing""" finished_at: Optional[datetime] = None - """The finished time of the job""" + """When the job finished processing""" + + status: ParsingJobStatus + """Current status of the job""" + + result: Optional[ExtractionResult] = None + """The result of an extraction job.""" + + error: Optional[Dict[str, object]] = None + """Error information if failed""" - object: Optional[Literal["job"]] = None - """The type of the object""" + json_schema: Dict[str, object] + """The JSON schema used for extraction""" diff --git a/src/mixedbread/types/extractions/extraction_result.py b/src/mixedbread/types/extractions/extraction_result.py index fbac5d5f..fdf23e83 100644 --- a/src/mixedbread/types/extractions/extraction_result.py +++ b/src/mixedbread/types/extractions/extraction_result.py @@ -1,5 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Dict, List from ..._models import BaseModel @@ -7,5 +8,8 @@ class ExtractionResult(BaseModel): - data: object - """The extracted data""" + """The result of an extraction job.""" + + data: Dict[str, object] + + warnings: List[str] diff --git a/src/mixedbread/types/extractions/image_url_input_param.py b/src/mixedbread/types/extractions/image_url_input_param.py new file mode 100644 index 00000000..cb631e56 --- /dev/null +++ b/src/mixedbread/types/extractions/image_url_input_param.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ImageURLInputParam", "ImageURL"] + + +class ImageURL(TypedDict, total=False): + """The image input specification.""" + + url: Required[str] + """The image URL. Can be either a URL or a Data URI.""" + + format: str + """The image format/mimetype""" + + +class ImageURLInputParam(TypedDict, total=False): + """Model for image input validation.""" + + type: Literal["image_url"] + """Input type identifier""" + + image_url: Required[ImageURL] + """The image input specification.""" diff --git a/src/mixedbread/types/extractions/job_create_params.py b/src/mixedbread/types/extractions/job_create_params.py index 476154b2..a252df4a 100644 --- a/src/mixedbread/types/extractions/job_create_params.py +++ b/src/mixedbread/types/extractions/job_create_params.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import Dict from typing_extensions import Required, TypedDict __all__ = ["JobCreateParams"] @@ -11,5 +12,5 @@ class JobCreateParams(TypedDict, total=False): file_id: Required[str] """The ID of the file to extract from""" - json_schema: Required[object] + json_schema: Required[Dict[str, object]] """The JSON schema to use for extraction""" diff --git a/src/mixedbread/types/extractions/schema_enhance_params.py b/src/mixedbread/types/extractions/schema_enhance_params.py index e0facf34..8526c898 100644 --- a/src/mixedbread/types/extractions/schema_enhance_params.py +++ b/src/mixedbread/types/extractions/schema_enhance_params.py @@ -2,11 +2,12 @@ from __future__ import annotations +from typing import Dict from typing_extensions import Required, TypedDict __all__ = ["SchemaEnhanceParams"] class SchemaEnhanceParams(TypedDict, total=False): - json_schema: Required[object] + json_schema: Required[Dict[str, object]] """The JSON schema to enhance""" diff --git a/src/mixedbread/types/extractions/schema_validate_params.py b/src/mixedbread/types/extractions/schema_validate_params.py index 947182ca..96a31b4f 100644 --- a/src/mixedbread/types/extractions/schema_validate_params.py +++ b/src/mixedbread/types/extractions/schema_validate_params.py @@ -2,11 +2,12 @@ from __future__ import annotations +from typing import Dict from typing_extensions import Required, TypedDict __all__ = ["SchemaValidateParams"] class SchemaValidateParams(TypedDict, total=False): - json_schema: Required[object] + json_schema: Required[Dict[str, object]] """The JSON schema to validate""" diff --git a/src/mixedbread/types/extractions/text_input_param.py b/src/mixedbread/types/extractions/text_input_param.py new file mode 100644 index 00000000..9b459cf5 --- /dev/null +++ b/src/mixedbread/types/extractions/text_input_param.py @@ -0,0 +1,22 @@ +# 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__ = ["TextInputParam"] + + +class TextInputParam(TypedDict, total=False): + """Model for text input validation. + + Attributes: + type: Input type identifier, always "text" + text: The actual text content, with length and whitespace constraints + """ + + type: Literal["text"] + """Input type identifier""" + + text: Required[str] + """Text content to process""" diff --git a/src/mixedbread/types/extractions/validated_json_schema.py b/src/mixedbread/types/extractions/validated_json_schema.py index 42e6da5b..79b2dff5 100644 --- a/src/mixedbread/types/extractions/validated_json_schema.py +++ b/src/mixedbread/types/extractions/validated_json_schema.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import Dict, List from ..._models import BaseModel @@ -8,11 +8,13 @@ class ValidatedJsonSchema(BaseModel): - errors: List[str] - """List of validation errors""" + """Result of validating a JSON schema.""" is_valid: bool """Whether the schema is valid""" - json_schema: object + errors: List[str] + """List of validation errors""" + + json_schema: Dict[str, object] """The validated JSON schema""" diff --git a/src/mixedbread/types/file_deleted.py b/src/mixedbread/types/file_delete_response.py similarity index 85% rename from src/mixedbread/types/file_deleted.py rename to src/mixedbread/types/file_delete_response.py index 5557b674..1cc6bbb7 100644 --- a/src/mixedbread/types/file_deleted.py +++ b/src/mixedbread/types/file_delete_response.py @@ -5,10 +5,10 @@ from .._models import BaseModel -__all__ = ["FileDeleted"] +__all__ = ["FileDeleteResponse"] -class FileDeleted(BaseModel): +class FileDeleteResponse(BaseModel): id: str """The ID of the deleted file""" diff --git a/src/mixedbread/types/file_list_params.py b/src/mixedbread/types/file_list_params.py index 7ecfdf54..83207e3d 100644 --- a/src/mixedbread/types/file_list_params.py +++ b/src/mixedbread/types/file_list_params.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import Optional from typing_extensions import TypedDict __all__ = ["FileListParams"] @@ -9,7 +10,22 @@ class FileListParams(TypedDict, total=False): limit: int - """Maximum number of items to return per page""" + """Maximum number of items to return per page (1-100)""" - offset: int - """Offset of the first item to return""" + after: Optional[str] + """Cursor for forward pagination - get items after this position. + + Use last_cursor from previous response. + """ + + before: Optional[str] + """Cursor for backward pagination - get items before this position. + + Use first_cursor from previous response. + """ + + include_total: bool + """Whether to include total count in response (expensive operation)""" + + q: Optional[str] + """Search query for fuzzy matching over name and description fields""" diff --git a/src/mixedbread/types/file_object.py b/src/mixedbread/types/file_object.py index 0b20b6fe..f677bd7f 100644 --- a/src/mixedbread/types/file_object.py +++ b/src/mixedbread/types/file_object.py @@ -8,23 +8,29 @@ class FileObject(BaseModel): - id: str - """Unique identifier for the file""" + """A model representing a file object in the system. - bytes: int - """Size of the file in bytes""" + This model contains metadata about files stored in the system, including + identifiers, size information, and timestamps. + """ - created_at: datetime - """Timestamp when the file was created""" + id: str + """Unique identifier for the file""" filename: str """Name of the file including extension""" + bytes: int + """Size of the file in bytes""" + mime_type: str """MIME type of the file""" - updated_at: datetime - """Timestamp when the file was last updated""" - version: int """Version of the file""" + + created_at: datetime + """Timestamp when the file was created""" + + updated_at: datetime + """Timestamp when the file was last updated""" diff --git a/src/mixedbread/types/files/__init__.py b/src/mixedbread/types/files/__init__.py index f8ee8b14..5b8dff28 100644 --- a/src/mixedbread/types/files/__init__.py +++ b/src/mixedbread/types/files/__init__.py @@ -1,3 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations + +from .upload_create_params import UploadCreateParams as UploadCreateParams +from .upload_list_response import UploadListResponse as UploadListResponse +from .multipart_upload_part import MultipartUploadPart as MultipartUploadPart +from .upload_abort_response import UploadAbortResponse as UploadAbortResponse +from .upload_complete_params import UploadCompleteParams as UploadCompleteParams +from .upload_create_response import UploadCreateResponse as UploadCreateResponse +from .upload_retrieve_response import UploadRetrieveResponse as UploadRetrieveResponse +from .multipart_upload_part_url import MultipartUploadPartURL as MultipartUploadPartURL +from .multipart_upload_part_param import MultipartUploadPartParam as MultipartUploadPartParam diff --git a/src/mixedbread/types/files/multipart_upload_part.py b/src/mixedbread/types/files/multipart_upload_part.py new file mode 100644 index 00000000..f3f6a226 --- /dev/null +++ b/src/mixedbread/types/files/multipart_upload_part.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["MultipartUploadPart"] + + +class MultipartUploadPart(BaseModel): + part_number: int + """1-based part number""" + + etag: str + """ETag returned by the storage backend after uploading the part""" diff --git a/src/mixedbread/types/files/multipart_upload_part_param.py b/src/mixedbread/types/files/multipart_upload_part_param.py new file mode 100644 index 00000000..78552c46 --- /dev/null +++ b/src/mixedbread/types/files/multipart_upload_part_param.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 Required, TypedDict + +__all__ = ["MultipartUploadPartParam"] + + +class MultipartUploadPartParam(TypedDict, total=False): + part_number: Required[int] + """1-based part number""" + + etag: Required[str] + """ETag returned by the storage backend after uploading the part""" diff --git a/src/mixedbread/types/files/multipart_upload_part_url.py b/src/mixedbread/types/files/multipart_upload_part_url.py new file mode 100644 index 00000000..ed51231e --- /dev/null +++ b/src/mixedbread/types/files/multipart_upload_part_url.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["MultipartUploadPartURL"] + + +class MultipartUploadPartURL(BaseModel): + part_number: int + """1-based part number""" + + url: str + """Presigned URL for uploading this part""" diff --git a/src/mixedbread/types/files/upload_abort_response.py b/src/mixedbread/types/files/upload_abort_response.py new file mode 100644 index 00000000..aa8f02db --- /dev/null +++ b/src/mixedbread/types/files/upload_abort_response.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["UploadAbortResponse"] + + +class UploadAbortResponse(BaseModel): + id: str + """The ID of the deleted file""" + + deleted: Optional[bool] = None + """Whether the file was deleted""" + + object: Optional[Literal["file"]] = None + """The type of the deleted object""" diff --git a/src/mixedbread/types/files/upload_complete_params.py b/src/mixedbread/types/files/upload_complete_params.py new file mode 100644 index 00000000..6e59c5a4 --- /dev/null +++ b/src/mixedbread/types/files/upload_complete_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 import Iterable +from typing_extensions import Required, TypedDict + +from .multipart_upload_part_param import MultipartUploadPartParam + +__all__ = ["UploadCompleteParams"] + + +class UploadCompleteParams(TypedDict, total=False): + parts: Required[Iterable[MultipartUploadPartParam]] + """List of completed parts with their ETags""" diff --git a/src/mixedbread/types/files/upload_create_params.py b/src/mixedbread/types/files/upload_create_params.py new file mode 100644 index 00000000..8ce2656e --- /dev/null +++ b/src/mixedbread/types/files/upload_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_extensions import Required, TypedDict + +__all__ = ["UploadCreateParams"] + + +class UploadCreateParams(TypedDict, total=False): + filename: Required[str] + """Name of the file including extension""" + + file_size: Required[int] + """Total size of the file in bytes""" + + mime_type: Required[str] + """MIME type of the file""" + + part_count: int + """Number of parts to split the upload into""" diff --git a/src/mixedbread/types/files/upload_create_response.py b/src/mixedbread/types/files/upload_create_response.py new file mode 100644 index 00000000..30a6dc0c --- /dev/null +++ b/src/mixedbread/types/files/upload_create_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel +from .multipart_upload_part_url import MultipartUploadPartURL + +__all__ = ["UploadCreateResponse"] + + +class UploadCreateResponse(BaseModel): + id: str + """The multipart upload ID (use this to complete or abort)""" + + part_urls: List[MultipartUploadPartURL] + """Presigned URLs for uploading parts""" diff --git a/src/mixedbread/types/files/upload_list_response.py b/src/mixedbread/types/files/upload_list_response.py new file mode 100644 index 00000000..f44a8e14 --- /dev/null +++ b/src/mixedbread/types/files/upload_list_response.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel + +__all__ = ["UploadListResponse", "Data"] + + +class Data(BaseModel): + id: str + """The multipart upload record ID""" + + filename: str + """Original filename""" + + file_size: int + """Total file size in bytes""" + + mime_type: str + """MIME type of the file""" + + part_count: int + """Number of parts the file was split into""" + + created_at: str + """When the upload was initiated""" + + +class UploadListResponse(BaseModel): + data: List[Data] + """List of in-progress multipart uploads""" diff --git a/src/mixedbread/types/files/upload_retrieve_response.py b/src/mixedbread/types/files/upload_retrieve_response.py new file mode 100644 index 00000000..eda17521 --- /dev/null +++ b/src/mixedbread/types/files/upload_retrieve_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel +from .multipart_upload_part import MultipartUploadPart +from .multipart_upload_part_url import MultipartUploadPartURL + +__all__ = ["UploadRetrieveResponse"] + + +class UploadRetrieveResponse(BaseModel): + id: str + """The multipart upload record ID""" + + filename: str + """Original filename""" + + file_size: int + """Total file size in bytes""" + + mime_type: str + """MIME type of the file""" + + part_count: int + """Number of parts the file was split into""" + + created_at: str + """When the upload was initiated""" + + completed_parts: List[MultipartUploadPart] + """Parts that have already been uploaded""" + + part_urls: List[MultipartUploadPartURL] + """Presigned URLs for the parts that still need to be uploaded""" diff --git a/src/mixedbread/types/info_response.py b/src/mixedbread/types/info_response.py index fee93225..20844293 100644 --- a/src/mixedbread/types/info_response.py +++ b/src/mixedbread/types/info_response.py @@ -1,12 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["InfoResponse"] class InfoResponse(BaseModel): + """Info Pydantic Response Service Message""" + name: str version: str diff --git a/src/mixedbread/types/linear_data_source_param.py b/src/mixedbread/types/linear_data_source_param.py new file mode 100644 index 00000000..69c2b431 --- /dev/null +++ b/src/mixedbread/types/linear_data_source_param.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from .oauth2_params import Oauth2Params + +__all__ = ["LinearDataSourceParam"] + + +class LinearDataSourceParam(TypedDict, total=False): + """Parameters for creating or updating a Linear data source.""" + + type: Literal["linear"] + """The type of data source to create""" + + name: Required[str] + """The name of the data source""" + + metadata: object + """The metadata of the data source""" + + auth_params: Optional[Oauth2Params] + """Base class for OAuth2 create or update parameters.""" diff --git a/src/mixedbread/types/multi_encoding_embedding.py b/src/mixedbread/types/multi_encoding_embedding.py new file mode 100644 index 00000000..1d73d05c --- /dev/null +++ b/src/mixedbread/types/multi_encoding_embedding.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import builtins +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["MultiEncodingEmbedding", "Embedding"] + + +class Embedding(BaseModel): + """ + The encoded embedding data by encoding format.Returned, if more than one encoding format is used. + """ + + float: Optional[List[builtins.float]] = None + + int8: Optional[List[int]] = None + + uint8: Optional[List[int]] = None + + binary: Optional[List[int]] = None + + ubinary: Optional[List[int]] = None + + base64: Optional[str] = None + + +class MultiEncodingEmbedding(BaseModel): + embedding: Embedding + """ + The encoded embedding data by encoding format.Returned, if more than one + encoding format is used. + """ + + index: int + """The index of the embedding.""" + + object: Optional[Literal["embedding_dict"]] = None + """The object type of the embedding.""" diff --git a/src/mixedbread/types/notion_data_source_param.py b/src/mixedbread/types/notion_data_source_param.py new file mode 100644 index 00000000..12409ef6 --- /dev/null +++ b/src/mixedbread/types/notion_data_source_param.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 Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .oauth2_params import Oauth2Params + +__all__ = ["NotionDataSourceParam", "AuthParams", "AuthParamsAPIKeyCreateOrUpdateParams"] + + +class AuthParamsAPIKeyCreateOrUpdateParams(TypedDict, total=False): + """Base class for API key create or update parameters.""" + + type: Literal["api_key"] + + api_key: Required[str] + """The API key""" + + +AuthParams: TypeAlias = Union[Oauth2Params, AuthParamsAPIKeyCreateOrUpdateParams] + + +class NotionDataSourceParam(TypedDict, total=False): + """Parameters for creating or updating a Notion data source.""" + + type: Literal["notion"] + """The type of data source to create""" + + name: Required[str] + """The name of the data source""" + + metadata: object + """The metadata of the data source""" + + auth_params: Optional[AuthParams] + """The authentication parameters of the data source. + + Notion supports OAuth2 and API key. + """ diff --git a/src/mixedbread/types/oauth2_params.py b/src/mixedbread/types/oauth2_params.py new file mode 100644 index 00000000..4bbc79f6 --- /dev/null +++ b/src/mixedbread/types/oauth2_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 Literal, TypedDict + +__all__ = ["Oauth2Params"] + + +class Oauth2Params(TypedDict, total=False): + """Base class for OAuth2 create or update parameters.""" + + type: Literal["oauth2"] diff --git a/src/mixedbread/types/file_list_response.py b/src/mixedbread/types/pagination_with_total.py similarity index 52% rename from src/mixedbread/types/file_list_response.py rename to src/mixedbread/types/pagination_with_total.py index defc63f1..07ad8fb0 100644 --- a/src/mixedbread/types/file_list_response.py +++ b/src/mixedbread/types/pagination_with_total.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_extensions import Literal +from typing import Optional from .._models import BaseModel -from .file_object import FileObject -__all__ = ["FileListResponse", "Pagination"] +__all__ = ["PaginationWithTotal"] -class Pagination(BaseModel): +class PaginationWithTotal(BaseModel): + """Pagination model that includes total count of items.""" + limit: Optional[int] = None """Maximum number of items to return per page""" @@ -18,14 +18,3 @@ class Pagination(BaseModel): total: Optional[int] = None """Total number of items available""" - - -class FileListResponse(BaseModel): - data: List[FileObject] - """The list of files""" - - pagination: Pagination - """Pagination model that includes total count of items.""" - - object: Optional[Literal["list"]] = None - """The object type of the response""" diff --git a/src/mixedbread/types/parsing/__init__.py b/src/mixedbread/types/parsing/__init__.py index 94393293..d3e38bbe 100644 --- a/src/mixedbread/types/parsing/__init__.py +++ b/src/mixedbread/types/parsing/__init__.py @@ -3,4 +3,11 @@ from __future__ import annotations from .parsing_job import ParsingJob as ParsingJob +from .element_type import ElementType as ElementType +from .return_format import ReturnFormat as ReturnFormat +from .job_list_params import JobListParams as JobListParams +from .chunking_strategy import ChunkingStrategy as ChunkingStrategy from .job_create_params import JobCreateParams as JobCreateParams +from .job_list_response import JobListResponse as JobListResponse +from .parsing_job_status import ParsingJobStatus as ParsingJobStatus +from .job_delete_response import JobDeleteResponse as JobDeleteResponse diff --git a/src/mixedbread/types/parsing/chunking_strategy.py b/src/mixedbread/types/parsing/chunking_strategy.py new file mode 100644 index 00000000..7848a25f --- /dev/null +++ b/src/mixedbread/types/parsing/chunking_strategy.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ChunkingStrategy"] + +ChunkingStrategy: TypeAlias = Literal["page"] diff --git a/src/mixedbread/types/parsing/element_type.py b/src/mixedbread/types/parsing/element_type.py new file mode 100644 index 00000000..e655f0b3 --- /dev/null +++ b/src/mixedbread/types/parsing/element_type.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ElementType"] + +ElementType: TypeAlias = Literal[ + "header", + "footer", + "title", + "section-header", + "page-number", + "list-item", + "figure", + "table", + "form", + "text", + "footnote", +] diff --git a/src/mixedbread/types/parsing/job_create_params.py b/src/mixedbread/types/parsing/job_create_params.py index a0d4602d..7f624260 100644 --- a/src/mixedbread/types/parsing/job_create_params.py +++ b/src/mixedbread/types/parsing/job_create_params.py @@ -5,6 +5,10 @@ from typing import List, Optional from typing_extensions import Literal, Required, TypedDict +from .element_type import ElementType +from .return_format import ReturnFormat +from .chunking_strategy import ChunkingStrategy + __all__ = ["JobCreateParams"] @@ -12,27 +16,14 @@ class JobCreateParams(TypedDict, total=False): file_id: Required[str] """The ID of the file to parse""" - chunking_strategy: Literal["page"] - """The strategy to use for chunking the content""" - - element_types: Optional[ - List[ - Literal[ - "caption", - "footnote", - "formula", - "list-item", - "page-footer", - "page-header", - "picture", - "section-header", - "table", - "text", - "title", - ] - ] - ] + element_types: Optional[List[ElementType]] """The elements to extract from the document""" - return_format: Literal["html", "markdown", "plain"] + chunking_strategy: ChunkingStrategy + """The strategy to use for chunking the content""" + + return_format: ReturnFormat """The format of the returned content""" + + mode: Literal["fast", "high_quality"] + """The strategy to use for OCR""" diff --git a/src/mixedbread/types/parsing/job_delete_response.py b/src/mixedbread/types/parsing/job_delete_response.py new file mode 100644 index 00000000..3f2cccf9 --- /dev/null +++ b/src/mixedbread/types/parsing/job_delete_response.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["JobDeleteResponse"] + + +class JobDeleteResponse(BaseModel): + """A deleted parsing job.""" + + id: str + """The ID of the deleted job""" + + deleted: Optional[bool] = None + """Whether the job was deleted""" + + object: Optional[Literal["parsing_job"]] = None + """The type of the object""" diff --git a/src/mixedbread/types/parsing/job_list_params.py b/src/mixedbread/types/parsing/job_list_params.py new file mode 100644 index 00000000..c34f3c09 --- /dev/null +++ b/src/mixedbread/types/parsing/job_list_params.py @@ -0,0 +1,36 @@ +# 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 TypedDict + +from .parsing_job_status import ParsingJobStatus + +__all__ = ["JobListParams"] + + +class JobListParams(TypedDict, total=False): + limit: int + """Maximum number of items to return per page (1-100)""" + + after: Optional[str] + """Cursor for forward pagination - get items after this position. + + Use last_cursor from previous response. + """ + + before: Optional[str] + """Cursor for backward pagination - get items before this position. + + Use first_cursor from previous response. + """ + + include_total: bool + """Whether to include total count in response (expensive operation)""" + + statuses: Optional[List[ParsingJobStatus]] + """Status to filter by""" + + q: Optional[str] + """Search query to filter by""" diff --git a/src/mixedbread/types/parsing/job_list_response.py b/src/mixedbread/types/parsing/job_list_response.py new file mode 100644 index 00000000..972537f9 --- /dev/null +++ b/src/mixedbread/types/parsing/job_list_response.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel +from .parsing_job_status import ParsingJobStatus + +__all__ = ["JobListResponse"] + + +class JobListResponse(BaseModel): + """A parsing job item for list responses.""" + + id: str + """The ID of the job""" + + file_id: str + """The ID of the file to parse""" + + filename: Optional[str] = None + """The name of the file""" + + status: ParsingJobStatus + """The status of the job""" + + error: Optional[Dict[str, object]] = None + """The error of the job""" + + started_at: Optional[datetime] = None + """The started time of the job""" + + finished_at: Optional[datetime] = None + """The finished time of the job""" + + created_at: Optional[datetime] = None + """The creation time of the job""" + + updated_at: Optional[datetime] = None + """The updated time of the job""" + + object: Optional[Literal["parsing_job"]] = None + """The type of the object""" diff --git a/src/mixedbread/types/parsing/parsing_job.py b/src/mixedbread/types/parsing/parsing_job.py index e457e260..2f3d250d 100644 --- a/src/mixedbread/types/parsing/parsing_job.py +++ b/src/mixedbread/types/parsing/parsing_job.py @@ -1,103 +1,107 @@ # 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 +from .element_type import ElementType +from .return_format import ReturnFormat +from .chunking_strategy import ChunkingStrategy +from .parsing_job_status import ParsingJobStatus __all__ = ["ParsingJob", "Result", "ResultChunk", "ResultChunkElement"] class ResultChunkElement(BaseModel): - bbox: List[object] - """The bounding box coordinates [x1, y1, x2, y2]""" + """Represents an extracted element from a document with its content and metadata.""" + + type: ElementType + """The type of the extracted element""" confidence: float """The confidence score of the extraction""" - content: str - """The full content of the extracted element""" + bbox: List[object] + """The bounding box coordinates [x1, y1, x2, y2]""" page: int """The page number where the element was found""" - type: Literal[ - "caption", - "footnote", - "formula", - "list-item", - "page-footer", - "page-header", - "picture", - "section-header", - "table", - "text", - "title", - ] - """The type of the extracted element""" + content: str + """The extracted text content of the element""" summary: Optional[str] = None """A brief summary of the element's content""" + image: Optional[str] = None + """The base64-encoded image data for figure elements""" + class ResultChunk(BaseModel): - content: str + """A chunk of text extracted from a document page.""" + + content: Optional[str] = None """The full content of the chunk""" content_to_embed: str - """The content to be used for embedding""" + """The content of the chunk to embed""" elements: List[ResultChunkElement] """List of elements contained in this chunk""" class Result(BaseModel): - chunking_strategy: Literal["page"] + """Result of document parsing operation.""" + + chunking_strategy: ChunkingStrategy """The strategy used for chunking the document""" - chunks: List[ResultChunk] - """List of extracted chunks from the document""" + return_format: ReturnFormat + """The format of the returned content""" - element_types: List[ - Literal[ - "caption", - "footnote", - "formula", - "list-item", - "page-footer", - "page-header", - "picture", - "section-header", - "table", - "text", - "title", - ] - ] + element_types: List[ElementType] """The types of elements extracted""" - return_format: Literal["html", "markdown", "plain"] - """The format of the returned content""" + chunks: List[ResultChunk] + """List of extracted chunks from the document""" + + page_sizes: Optional[List[List[object]]] = None + """List of (width, height) tuples for each page""" class ParsingJob(BaseModel): + """A job for parsing documents with its current state and result.""" + id: str """The ID of the job""" - status: Literal["none", "running", "canceled", "successful", "failed", "resumable", "pending"] + file_id: str + """The ID of the file to parse""" + + filename: Optional[str] = None + """The name of the file""" + + status: ParsingJobStatus """The status of the job""" - created_at: Optional[datetime] = None - """The creation time of the job""" + error: Optional[Dict[str, object]] = None + """The error of the job""" + + result: Optional[Result] = None + """Result of document parsing operation.""" - errors: Optional[List[str]] = None - """The errors of the job""" + started_at: Optional[datetime] = None + """The started time of the job""" finished_at: Optional[datetime] = None """The finished time of the job""" - object: Optional[Literal["job"]] = None - """The type of the object""" + created_at: Optional[datetime] = None + """The creation time of the job""" - result: Optional[Result] = None - """Result of document parsing operation.""" + updated_at: Optional[datetime] = None + """The updated time of the job""" + + object: Optional[Literal["parsing_job"]] = None + """The type of the object""" diff --git a/src/mixedbread/types/parsing/parsing_job_status.py b/src/mixedbread/types/parsing/parsing_job_status.py new file mode 100644 index 00000000..03e03a48 --- /dev/null +++ b/src/mixedbread/types/parsing/parsing_job_status.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ParsingJobStatus"] + +ParsingJobStatus: TypeAlias = Literal["pending", "in_progress", "cancelled", "completed", "failed"] diff --git a/src/mixedbread/types/parsing/return_format.py b/src/mixedbread/types/parsing/return_format.py new file mode 100644 index 00000000..2b080722 --- /dev/null +++ b/src/mixedbread/types/parsing/return_format.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ReturnFormat"] + +ReturnFormat: TypeAlias = Literal["html", "markdown", "plain"] diff --git a/src/mixedbread/types/rerank_response.py b/src/mixedbread/types/rerank_response.py new file mode 100644 index 00000000..73ebfaaa --- /dev/null +++ b/src/mixedbread/types/rerank_response.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.usage import Usage + +__all__ = ["RerankResponse", "Data"] + + +class Data(BaseModel): + index: int + """The index of the document.""" + + score: float + """The score of the document.""" + + input: Optional[object] = None + """The input document.""" + + object: Optional[Literal["rank_result"]] = None + """The object type.""" + + +class RerankResponse(BaseModel): + usage: Usage + """The usage of the model""" + + model: str + """The model used""" + + data: List[Data] + """The ranked documents.""" + + object: Optional[Literal["list"]] = None + """The object type of the response""" + + top_k: int + """The number of documents to return.""" + + return_input: bool + """Whether to return the documents.""" diff --git a/src/mixedbread/types/reranking_create_response.py b/src/mixedbread/types/reranking_create_response.py deleted file mode 100644 index 28710545..00000000 --- a/src/mixedbread/types/reranking_create_response.py +++ /dev/null @@ -1,76 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["RerankingCreateResponse", "Data", "Usage"] - - -class Data(BaseModel): - index: int - - input: object - """The input document.""" - - score: float - """The score of the document.""" - - object: Optional[ - Literal[ - "list", - "job", - "embedding", - "embedding_dict", - "text_document", - "file", - "vector_store", - "vector_store.file", - "api_key", - ] - ] = None - """The object type.""" - - -class Usage(BaseModel): - prompt_tokens: int - """The number of tokens used for the prompt""" - - total_tokens: int - """The total number of tokens used""" - - completion_tokens: Optional[int] = None - """The number of tokens used for the completion""" - - -class RerankingCreateResponse(BaseModel): - data: List[Data] - """The ranked documents.""" - - model: str - """The model used""" - - return_input: bool - """Whether to return the documents.""" - - top_k: int - """The number of documents to return.""" - - usage: Usage - """The usage of the model""" - - object: Optional[ - Literal[ - "list", - "job", - "embedding", - "embedding_dict", - "text_document", - "file", - "vector_store", - "vector_store.file", - "api_key", - ] - ] = None - """The object type of the response""" diff --git a/src/mixedbread/types/scored_audio_url_input_chunk.py b/src/mixedbread/types/scored_audio_url_input_chunk.py new file mode 100644 index 00000000..95292f9a --- /dev/null +++ b/src/mixedbread/types/scored_audio_url_input_chunk.py @@ -0,0 +1,315 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "ScoredAudioURLInputChunk", + "GeneratedMetadata", + "GeneratedMetadataMarkdownChunkGeneratedMetadata", + "GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading", + "GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext", + "GeneratedMetadataTextChunkGeneratedMetadata", + "GeneratedMetadataPdfChunkGeneratedMetadata", + "GeneratedMetadataCodeChunkGeneratedMetadata", + "GeneratedMetadataAudioChunkGeneratedMetadata", + "GeneratedMetadataVideoChunkGeneratedMetadata", + "GeneratedMetadataImageChunkGeneratedMetadata", + "AudioURL", +] + + +class GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading(BaseModel): + level: int + + text: str + + +class GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext(BaseModel): + level: int + + text: str + + +class GeneratedMetadataMarkdownChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["markdown"]] = None + + file_type: Optional[Literal["text/markdown"]] = None + + language: str + + word_count: int + + file_size: int + + chunk_headings: Optional[List[GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading]] = None + + heading_context: Optional[List[GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext]] = None + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + frontmatter: Optional[Dict[str, object]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataTextChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["text"]] = None + + file_type: Optional[Literal["text/plain"]] = None + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataPdfChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["pdf"]] = None + + file_type: Optional[Literal["application/pdf"]] = None + + total_pages: int + + total_size: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataCodeChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["code"]] = None + + file_type: str + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataAudioChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["audio"]] = None + + file_type: str + + file_size: int + + total_duration_seconds: float + + sample_rate: int + + channels: int + + audio_format: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataVideoChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["video"]] = None + + file_type: str + + file_size: Optional[int] = None + + total_duration_seconds: float + + fps: float + + width: int + + height: int + + frame_count: int + + has_audio_stream: Optional[bool] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataImageChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["image"]] = None + + file_type: str + + file_size: int + + width: int + + height: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +GeneratedMetadata: TypeAlias = Annotated[ + Union[ + GeneratedMetadataMarkdownChunkGeneratedMetadata, + GeneratedMetadataTextChunkGeneratedMetadata, + GeneratedMetadataPdfChunkGeneratedMetadata, + GeneratedMetadataCodeChunkGeneratedMetadata, + GeneratedMetadataAudioChunkGeneratedMetadata, + GeneratedMetadataVideoChunkGeneratedMetadata, + GeneratedMetadataImageChunkGeneratedMetadata, + None, + ], + PropertyInfo(discriminator="type"), +] + + +class AudioURL(BaseModel): + """Model for audio URL validation.""" + + url: str + """The audio URL. Can be either a URL or a Data URI.""" + + +class ScoredAudioURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[GeneratedMetadata] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + score: float + """score of the chunk""" + + file_id: str + """file id""" + + filename: str + """filename""" + + store_id: str + """store id""" + + external_id: Optional[str] = None + """external identifier for this file""" + + metadata: Optional[object] = None + """file metadata""" + + type: Optional[Literal["audio_url"]] = None + """Input type identifier""" + + transcription: Optional[str] = None + """speech recognition (sr) text of the audio""" + + summary: Optional[str] = None + """summary of the audio""" + + audio_url: Optional[AudioURL] = None + """Model for audio URL validation.""" + + sampling_rate: int + """The sampling rate of the audio.""" diff --git a/src/mixedbread/types/scored_image_url_input_chunk.py b/src/mixedbread/types/scored_image_url_input_chunk.py new file mode 100644 index 00000000..1a8cf548 --- /dev/null +++ b/src/mixedbread/types/scored_image_url_input_chunk.py @@ -0,0 +1,315 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "ScoredImageURLInputChunk", + "GeneratedMetadata", + "GeneratedMetadataMarkdownChunkGeneratedMetadata", + "GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading", + "GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext", + "GeneratedMetadataTextChunkGeneratedMetadata", + "GeneratedMetadataPdfChunkGeneratedMetadata", + "GeneratedMetadataCodeChunkGeneratedMetadata", + "GeneratedMetadataAudioChunkGeneratedMetadata", + "GeneratedMetadataVideoChunkGeneratedMetadata", + "GeneratedMetadataImageChunkGeneratedMetadata", + "ImageURL", +] + + +class GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading(BaseModel): + level: int + + text: str + + +class GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext(BaseModel): + level: int + + text: str + + +class GeneratedMetadataMarkdownChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["markdown"]] = None + + file_type: Optional[Literal["text/markdown"]] = None + + language: str + + word_count: int + + file_size: int + + chunk_headings: Optional[List[GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading]] = None + + heading_context: Optional[List[GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext]] = None + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + frontmatter: Optional[Dict[str, object]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataTextChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["text"]] = None + + file_type: Optional[Literal["text/plain"]] = None + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataPdfChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["pdf"]] = None + + file_type: Optional[Literal["application/pdf"]] = None + + total_pages: int + + total_size: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataCodeChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["code"]] = None + + file_type: str + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataAudioChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["audio"]] = None + + file_type: str + + file_size: int + + total_duration_seconds: float + + sample_rate: int + + channels: int + + audio_format: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataVideoChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["video"]] = None + + file_type: str + + file_size: Optional[int] = None + + total_duration_seconds: float + + fps: float + + width: int + + height: int + + frame_count: int + + has_audio_stream: Optional[bool] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataImageChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["image"]] = None + + file_type: str + + file_size: int + + width: int + + height: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +GeneratedMetadata: TypeAlias = Annotated[ + Union[ + GeneratedMetadataMarkdownChunkGeneratedMetadata, + GeneratedMetadataTextChunkGeneratedMetadata, + GeneratedMetadataPdfChunkGeneratedMetadata, + GeneratedMetadataCodeChunkGeneratedMetadata, + GeneratedMetadataAudioChunkGeneratedMetadata, + GeneratedMetadataVideoChunkGeneratedMetadata, + GeneratedMetadataImageChunkGeneratedMetadata, + None, + ], + PropertyInfo(discriminator="type"), +] + + +class ImageURL(BaseModel): + """Model for image URL validation.""" + + url: str + """The image URL. Can be either a URL or a Data URI.""" + + format: Optional[str] = None + """The image format/mimetype""" + + +class ScoredImageURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[GeneratedMetadata] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + score: float + """score of the chunk""" + + file_id: str + """file id""" + + filename: str + """filename""" + + store_id: str + """store id""" + + external_id: Optional[str] = None + """external identifier for this file""" + + metadata: Optional[object] = None + """file metadata""" + + type: Optional[Literal["image_url"]] = None + """Input type identifier""" + + ocr_text: Optional[str] = None + """ocr text of the image""" + + summary: Optional[str] = None + """summary of the image""" + + image_url: Optional[ImageURL] = None + """Model for image URL validation.""" diff --git a/src/mixedbread/types/scored_text_input_chunk.py b/src/mixedbread/types/scored_text_input_chunk.py new file mode 100644 index 00000000..41b33ea5 --- /dev/null +++ b/src/mixedbread/types/scored_text_input_chunk.py @@ -0,0 +1,301 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "ScoredTextInputChunk", + "GeneratedMetadata", + "GeneratedMetadataMarkdownChunkGeneratedMetadata", + "GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading", + "GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext", + "GeneratedMetadataTextChunkGeneratedMetadata", + "GeneratedMetadataPdfChunkGeneratedMetadata", + "GeneratedMetadataCodeChunkGeneratedMetadata", + "GeneratedMetadataAudioChunkGeneratedMetadata", + "GeneratedMetadataVideoChunkGeneratedMetadata", + "GeneratedMetadataImageChunkGeneratedMetadata", +] + + +class GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading(BaseModel): + level: int + + text: str + + +class GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext(BaseModel): + level: int + + text: str + + +class GeneratedMetadataMarkdownChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["markdown"]] = None + + file_type: Optional[Literal["text/markdown"]] = None + + language: str + + word_count: int + + file_size: int + + chunk_headings: Optional[List[GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading]] = None + + heading_context: Optional[List[GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext]] = None + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + frontmatter: Optional[Dict[str, object]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataTextChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["text"]] = None + + file_type: Optional[Literal["text/plain"]] = None + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataPdfChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["pdf"]] = None + + file_type: Optional[Literal["application/pdf"]] = None + + total_pages: int + + total_size: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataCodeChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["code"]] = None + + file_type: str + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataAudioChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["audio"]] = None + + file_type: str + + file_size: int + + total_duration_seconds: float + + sample_rate: int + + channels: int + + audio_format: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataVideoChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["video"]] = None + + file_type: str + + file_size: Optional[int] = None + + total_duration_seconds: float + + fps: float + + width: int + + height: int + + frame_count: int + + has_audio_stream: Optional[bool] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataImageChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["image"]] = None + + file_type: str + + file_size: int + + width: int + + height: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +GeneratedMetadata: TypeAlias = Annotated[ + Union[ + GeneratedMetadataMarkdownChunkGeneratedMetadata, + GeneratedMetadataTextChunkGeneratedMetadata, + GeneratedMetadataPdfChunkGeneratedMetadata, + GeneratedMetadataCodeChunkGeneratedMetadata, + GeneratedMetadataAudioChunkGeneratedMetadata, + GeneratedMetadataVideoChunkGeneratedMetadata, + GeneratedMetadataImageChunkGeneratedMetadata, + None, + ], + PropertyInfo(discriminator="type"), +] + + +class ScoredTextInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[GeneratedMetadata] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + score: float + """score of the chunk""" + + file_id: str + """file id""" + + filename: str + """filename""" + + store_id: str + """store id""" + + external_id: Optional[str] = None + """external identifier for this file""" + + metadata: Optional[object] = None + """file metadata""" + + type: Optional[Literal["text"]] = None + """Input type identifier""" + + offset: Optional[int] = None + """The offset of the text in the file relative to the start of the file.""" + + text: Optional[str] = None + """Text content""" diff --git a/src/mixedbread/types/scored_video_url_input_chunk.py b/src/mixedbread/types/scored_video_url_input_chunk.py new file mode 100644 index 00000000..a33d0d3a --- /dev/null +++ b/src/mixedbread/types/scored_video_url_input_chunk.py @@ -0,0 +1,312 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "ScoredVideoURLInputChunk", + "GeneratedMetadata", + "GeneratedMetadataMarkdownChunkGeneratedMetadata", + "GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading", + "GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext", + "GeneratedMetadataTextChunkGeneratedMetadata", + "GeneratedMetadataPdfChunkGeneratedMetadata", + "GeneratedMetadataCodeChunkGeneratedMetadata", + "GeneratedMetadataAudioChunkGeneratedMetadata", + "GeneratedMetadataVideoChunkGeneratedMetadata", + "GeneratedMetadataImageChunkGeneratedMetadata", + "VideoURL", +] + + +class GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading(BaseModel): + level: int + + text: str + + +class GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext(BaseModel): + level: int + + text: str + + +class GeneratedMetadataMarkdownChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["markdown"]] = None + + file_type: Optional[Literal["text/markdown"]] = None + + language: str + + word_count: int + + file_size: int + + chunk_headings: Optional[List[GeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading]] = None + + heading_context: Optional[List[GeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext]] = None + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + frontmatter: Optional[Dict[str, object]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataTextChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["text"]] = None + + file_type: Optional[Literal["text/plain"]] = None + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataPdfChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["pdf"]] = None + + file_type: Optional[Literal["application/pdf"]] = None + + total_pages: int + + total_size: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataCodeChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["code"]] = None + + file_type: str + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataAudioChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["audio"]] = None + + file_type: str + + file_size: int + + total_duration_seconds: float + + sample_rate: int + + channels: int + + audio_format: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataVideoChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["video"]] = None + + file_type: str + + file_size: Optional[int] = None + + total_duration_seconds: float + + fps: float + + width: int + + height: int + + frame_count: int + + has_audio_stream: Optional[bool] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class GeneratedMetadataImageChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["image"]] = None + + file_type: str + + file_size: int + + width: int + + height: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +GeneratedMetadata: TypeAlias = Annotated[ + Union[ + GeneratedMetadataMarkdownChunkGeneratedMetadata, + GeneratedMetadataTextChunkGeneratedMetadata, + GeneratedMetadataPdfChunkGeneratedMetadata, + GeneratedMetadataCodeChunkGeneratedMetadata, + GeneratedMetadataAudioChunkGeneratedMetadata, + GeneratedMetadataVideoChunkGeneratedMetadata, + GeneratedMetadataImageChunkGeneratedMetadata, + None, + ], + PropertyInfo(discriminator="type"), +] + + +class VideoURL(BaseModel): + """Model for video URL validation.""" + + url: str + """The video URL. Can be either a URL or a Data URI.""" + + +class ScoredVideoURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[GeneratedMetadata] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + score: float + """score of the chunk""" + + file_id: str + """file id""" + + filename: str + """filename""" + + store_id: str + """store id""" + + external_id: Optional[str] = None + """external identifier for this file""" + + metadata: Optional[object] = None + """file metadata""" + + type: Optional[Literal["video_url"]] = None + """Input type identifier""" + + transcription: Optional[str] = None + """speech recognition (sr) text of the video""" + + summary: Optional[str] = None + """summary of the video""" + + video_url: Optional[VideoURL] = None + """Model for video URL validation.""" diff --git a/src/mixedbread/types/shared/__init__.py b/src/mixedbread/types/shared/__init__.py new file mode 100644 index 00000000..66d5dcf9 --- /dev/null +++ b/src/mixedbread/types/shared/__init__.py @@ -0,0 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .usage import Usage as Usage +from .search_filter import SearchFilter as SearchFilter +from .search_filter_condition import SearchFilterCondition as SearchFilterCondition diff --git a/src/mixedbread/types/shared/search_filter.py b/src/mixedbread/types/shared/search_filter.py new file mode 100644 index 00000000..c502e6db --- /dev/null +++ b/src/mixedbread/types/shared/search_filter.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Union, Optional +from typing_extensions import TypeAlias, TypeAliasType + +from ..._compat import PYDANTIC_V1 +from ..._models import BaseModel +from .search_filter_condition import SearchFilterCondition + +__all__ = ["SearchFilter", "All", "Any", "NoneType"] + +if TYPE_CHECKING or not PYDANTIC_V1: + All = TypeAliasType("All", Union["SearchFilter", SearchFilterCondition]) +else: + All: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +if TYPE_CHECKING or not PYDANTIC_V1: + Any = TypeAliasType("Any", Union["SearchFilter", SearchFilterCondition]) +else: + Any: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +if TYPE_CHECKING or not PYDANTIC_V1: + NoneType = TypeAliasType("NoneType", Union["SearchFilter", SearchFilterCondition]) +else: + NoneType: TypeAlias = Union["SearchFilter", SearchFilterCondition] + + +class SearchFilter(BaseModel): + """Represents a filter with AND, OR, and NOT conditions.""" + + all: Optional[List[All]] = None + """List of conditions or filters to be ANDed together""" + + any: Optional[List[Any]] = None + """List of conditions or filters to be ORed together""" + + none: Optional[List[NoneType]] = None + """List of conditions or filters to be NOTed""" diff --git a/src/mixedbread/types/shared/search_filter_condition.py b/src/mixedbread/types/shared/search_filter_condition.py new file mode 100644 index 00000000..f50a91e1 --- /dev/null +++ b/src/mixedbread/types/shared/search_filter_condition.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["SearchFilterCondition"] + + +class SearchFilterCondition(BaseModel): + """Represents a condition with a field, operator, and value.""" + + key: str + """The field to apply the condition on""" + + value: object + """The value to compare against""" + + operator: Literal[ + "eq", "not_eq", "gt", "gte", "lt", "lte", "in", "not_in", "like", "starts_with", "not_like", "regex" + ] + """The operator for the condition""" diff --git a/src/mixedbread/types/shared/usage.py b/src/mixedbread/types/shared/usage.py new file mode 100644 index 00000000..eab91a6c --- /dev/null +++ b/src/mixedbread/types/shared/usage.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["Usage"] + + +class Usage(BaseModel): + prompt_tokens: int + """The number of tokens used for the prompt""" + + total_tokens: int + """The total number of tokens used""" + + completion_tokens: Optional[int] = None + """The number of tokens used for the completion""" diff --git a/src/mixedbread/types/shared_params/__init__.py b/src/mixedbread/types/shared_params/__init__.py new file mode 100644 index 00000000..c91e740d --- /dev/null +++ b/src/mixedbread/types/shared_params/__init__.py @@ -0,0 +1,4 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .search_filter import SearchFilter as SearchFilter +from .search_filter_condition import SearchFilterCondition as SearchFilterCondition diff --git a/src/mixedbread/types/shared_params/search_filter.py b/src/mixedbread/types/shared_params/search_filter.py new file mode 100644 index 00000000..fd468f38 --- /dev/null +++ b/src/mixedbread/types/shared_params/search_filter.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 TYPE_CHECKING, Union, Iterable, Optional +from typing_extensions import TypeAlias, TypedDict, TypeAliasType + +from ..._compat import PYDANTIC_V1 +from .search_filter_condition import SearchFilterCondition + +__all__ = ["SearchFilter", "All", "Any", "NoneType"] + +if TYPE_CHECKING or not PYDANTIC_V1: + All = TypeAliasType("All", Union["SearchFilter", SearchFilterCondition]) +else: + All: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +if TYPE_CHECKING or not PYDANTIC_V1: + Any = TypeAliasType("Any", Union["SearchFilter", SearchFilterCondition]) +else: + Any: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +if TYPE_CHECKING or not PYDANTIC_V1: + NoneType = TypeAliasType("NoneType", Union["SearchFilter", SearchFilterCondition]) +else: + NoneType: TypeAlias = Union["SearchFilter", SearchFilterCondition] + + +class SearchFilter(TypedDict, total=False): + """Represents a filter with AND, OR, and NOT conditions.""" + + all: Optional[Iterable[All]] + """List of conditions or filters to be ANDed together""" + + any: Optional[Iterable[Any]] + """List of conditions or filters to be ORed together""" + + none: Optional[Iterable[NoneType]] + """List of conditions or filters to be NOTed""" diff --git a/src/mixedbread/types/shared_params/search_filter_condition.py b/src/mixedbread/types/shared_params/search_filter_condition.py new file mode 100644 index 00000000..86efe345 --- /dev/null +++ b/src/mixedbread/types/shared_params/search_filter_condition.py @@ -0,0 +1,22 @@ +# 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__ = ["SearchFilterCondition"] + + +class SearchFilterCondition(TypedDict, total=False): + """Represents a condition with a field, operator, and value.""" + + key: Required[str] + """The field to apply the condition on""" + + value: Required[object] + """The value to compare against""" + + operator: Required[ + Literal["eq", "not_eq", "gt", "gte", "lt", "lte", "in", "not_in", "like", "starts_with", "not_like", "regex"] + ] + """The operator for the condition""" diff --git a/src/mixedbread/types/store.py b/src/mixedbread/types/store.py new file mode 100644 index 00000000..83de8954 --- /dev/null +++ b/src/mixedbread/types/store.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias + +from .._models import BaseModel +from .expires_after import ExpiresAfter + +__all__ = ["Store", "Config", "ConfigContextualization", "ConfigContextualizationContextualizationConfig", "FileCounts"] + + +class ConfigContextualizationContextualizationConfig(BaseModel): + with_metadata: Union[bool, List[str], None] = None + """Include all metadata or specific fields in the contextualization. + + Supports dot notation for nested fields (e.g., 'author.name'). When True, all + metadata is included (flattened). When a list, only specified fields are + included. + """ + + +ConfigContextualization: TypeAlias = Union[bool, ConfigContextualizationContextualizationConfig] + + +class Config(BaseModel): + """Configuration for a store.""" + + contextualization: Optional[ConfigContextualization] = None + """Contextualize files with metadata""" + + save_content: Optional[bool] = None + """Whether to save original content in the store. + + When False, only vectors are indexed without the original content (index-only + mode). This is useful for data privacy. Note: Reranking is not supported when + content is not saved. + """ + + +class FileCounts(BaseModel): + """Counts of files in different states""" + + pending: Optional[int] = None + """Number of files waiting to be processed""" + + in_progress: Optional[int] = None + """Number of files currently being processed""" + + cancelled: Optional[int] = None + """Number of files whose processing was cancelled""" + + completed: Optional[int] = None + """Number of successfully processed files""" + + failed: Optional[int] = None + """Number of files that failed processing""" + + total: Optional[int] = None + """Total number of files""" + + +class Store(BaseModel): + """Model representing a store with its metadata and timestamps.""" + + id: str + """Unique identifier for the store""" + + name: str + """Name of the store""" + + description: Optional[str] = None + """Detailed description of the store's purpose and contents""" + + is_public: Optional[bool] = None + """Whether the store can be accessed by anyone with valid login credentials""" + + metadata: Optional[object] = None + """Additional metadata associated with the store""" + + config: Optional[Config] = None + """Configuration for a store.""" + + file_counts: Optional[FileCounts] = None + """Counts of files in different states""" + + expires_after: Optional[ExpiresAfter] = None + """Represents an expiration policy for a store.""" + + status: Optional[Literal["expired", "in_progress", "completed"]] = None + """Processing status of the store""" + + created_at: datetime + """Timestamp when the store was created""" + + updated_at: datetime + """Timestamp when the store was last updated""" + + last_active_at: Optional[datetime] = None + """Timestamp when the store was last used""" + + usage_bytes: Optional[int] = None + """Total storage usage in bytes""" + + usage_tokens: Optional[int] = None + """Total storage usage in tokens""" + + expires_at: Optional[datetime] = None + """Optional expiration timestamp for the store""" + + object: Optional[Literal["store"]] = None + """Type of the object""" diff --git a/src/mixedbread/types/store_chunk_search_options_param.py b/src/mixedbread/types/store_chunk_search_options_param.py new file mode 100644 index 00000000..90bb9816 --- /dev/null +++ b/src/mixedbread/types/store_chunk_search_options_param.py @@ -0,0 +1,73 @@ +# 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 TypeAlias, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["StoreChunkSearchOptionsParam", "Rerank", "RerankRerankConfig", "Agentic", "AgenticAgenticSearchConfig"] + + +class RerankRerankConfig(TypedDict, total=False): + """Represents a reranking configuration.""" + + model: str + """The name of the reranking model""" + + with_metadata: Union[bool, SequenceNotStr[str]] + """Whether to include metadata in the reranked results""" + + top_k: Optional[int] + """Maximum number of results to return after reranking. + + If None, returns all reranked results. + """ + + +Rerank: TypeAlias = Union[bool, RerankRerankConfig] + + +class AgenticAgenticSearchConfig(TypedDict, total=False): + """Configuration for agentic multi-query search.""" + + max_rounds: int + """Maximum number of search rounds""" + + queries_per_round: int + """Maximum queries per round""" + + +Agentic: TypeAlias = Union[bool, AgenticAgenticSearchConfig] + + +class StoreChunkSearchOptionsParam(TypedDict, total=False): + """Options for configuring store chunk searches.""" + + score_threshold: float + """Minimum similarity score threshold""" + + rewrite_query: bool + """Whether to rewrite the query. + + Ignored when agentic is enabled (the agent handles query decomposition). + """ + + rerank: Optional[Rerank] + """Whether to rerank results and optional reranking configuration. + + Ignored when agentic is enabled (the agent handles ranking). + """ + + agentic: Optional[Agentic] + """ + Whether to use agentic multi-query search with automatic query decomposition and + ranking. When enabled, rewrite_query and rerank options are ignored. + """ + + return_metadata: bool + """Whether to return file metadata""" + + apply_search_rules: bool + """Whether to apply search rules""" diff --git a/src/mixedbread/types/store_create_params.py b/src/mixedbread/types/store_create_params.py new file mode 100644 index 00000000..b7814cdd --- /dev/null +++ b/src/mixedbread/types/store_create_params.py @@ -0,0 +1,65 @@ +# 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 TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .expires_after_param import ExpiresAfterParam + +__all__ = ["StoreCreateParams", "Config", "ConfigContextualization", "ConfigContextualizationContextualizationConfig"] + + +class StoreCreateParams(TypedDict, total=False): + name: Optional[str] + """Name for the new store. + + Can only contain lowercase letters, numbers, periods (.), and hyphens (-). + """ + + description: Optional[str] + """Description of the store""" + + is_public: bool + """Whether the store can be accessed by anyone with valid login credentials""" + + expires_after: Optional[ExpiresAfterParam] + """Represents an expiration policy for a store.""" + + metadata: object + """Optional metadata key-value pairs""" + + config: Optional[Config] + """Configuration for a store.""" + + file_ids: Optional[SequenceNotStr[str]] + """Optional list of file IDs""" + + +class ConfigContextualizationContextualizationConfig(TypedDict, total=False): + with_metadata: Union[bool, SequenceNotStr[str]] + """Include all metadata or specific fields in the contextualization. + + Supports dot notation for nested fields (e.g., 'author.name'). When True, all + metadata is included (flattened). When a list, only specified fields are + included. + """ + + +ConfigContextualization: TypeAlias = Union[bool, ConfigContextualizationContextualizationConfig] + + +class Config(TypedDict, total=False): + """Configuration for a store.""" + + contextualization: ConfigContextualization + """Contextualize files with metadata""" + + save_content: bool + """Whether to save original content in the store. + + When False, only vectors are indexed without the original content (index-only + mode). This is useful for data privacy. Note: Reranking is not supported when + content is not saved. + """ diff --git a/src/mixedbread/types/vector_store_deleted.py b/src/mixedbread/types/store_delete_response.py similarity index 60% rename from src/mixedbread/types/vector_store_deleted.py rename to src/mixedbread/types/store_delete_response.py index 393f8977..5a57079e 100644 --- a/src/mixedbread/types/vector_store_deleted.py +++ b/src/mixedbread/types/store_delete_response.py @@ -5,15 +5,17 @@ from .._models import BaseModel -__all__ = ["VectorStoreDeleted"] +__all__ = ["StoreDeleteResponse"] -class VectorStoreDeleted(BaseModel): +class StoreDeleteResponse(BaseModel): + """Response model for store deletion.""" + id: str - """ID of the deleted vector store""" + """ID of the deleted store""" deleted: bool """Whether the deletion was successful""" - object: Optional[Literal["vector_store"]] = None + object: Optional[Literal["store"]] = None """Type of the deleted object""" diff --git a/src/mixedbread/types/store_list_params.py b/src/mixedbread/types/store_list_params.py new file mode 100644 index 00000000..addec411 --- /dev/null +++ b/src/mixedbread/types/store_list_params.py @@ -0,0 +1,31 @@ +# 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__ = ["StoreListParams"] + + +class StoreListParams(TypedDict, total=False): + limit: int + """Maximum number of items to return per page (1-100)""" + + after: Optional[str] + """Cursor for forward pagination - get items after this position. + + Use last_cursor from previous response. + """ + + before: Optional[str] + """Cursor for backward pagination - get items before this position. + + Use first_cursor from previous response. + """ + + include_total: bool + """Whether to include total count in response (expensive operation)""" + + q: Optional[str] + """Search query for fuzzy matching over name and description fields""" diff --git a/src/mixedbread/types/store_metadata_facets_params.py b/src/mixedbread/types/store_metadata_facets_params.py new file mode 100644 index 00000000..64aa124e --- /dev/null +++ b/src/mixedbread/types/store_metadata_facets_params.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .store_chunk_search_options_param import StoreChunkSearchOptionsParam +from .shared_params.search_filter_condition import SearchFilterCondition + +__all__ = ["StoreMetadataFacetsParams", "Filters", "FiltersUnionMember2"] + + +class StoreMetadataFacetsParams(TypedDict, total=False): + query: Optional[str] + """Search query text""" + + store_identifiers: Required[SequenceNotStr[str]] + """IDs or names of stores to search""" + + top_k: int + """Number of results to return""" + + filters: Optional[Filters] + """Optional filter conditions""" + + file_ids: Union[Iterable[object], SequenceNotStr[str], None] + """Optional list of file IDs to filter chunks by (inclusion filter)""" + + search_options: StoreChunkSearchOptionsParam + """Search configuration options""" + + facets: Optional[SequenceNotStr[str]] + """Optional list of facets to return. Use dot for nested fields.""" + + +FiltersUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +Filters: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[FiltersUnionMember2]] + +from .shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/store_metadata_facets_response.py b/src/mixedbread/types/store_metadata_facets_response.py new file mode 100644 index 00000000..6b993206 --- /dev/null +++ b/src/mixedbread/types/store_metadata_facets_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict + +from .._models import BaseModel + +__all__ = ["StoreMetadataFacetsResponse"] + + +class StoreMetadataFacetsResponse(BaseModel): + """Represents metadata facets for a store.""" + + facets: Dict[str, Dict[str, object]] + """Metadata facets""" diff --git a/src/mixedbread/types/store_question_answering_params.py b/src/mixedbread/types/store_question_answering_params.py new file mode 100644 index 00000000..26abbb4e --- /dev/null +++ b/src/mixedbread/types/store_question_answering_params.py @@ -0,0 +1,59 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .store_chunk_search_options_param import StoreChunkSearchOptionsParam +from .shared_params.search_filter_condition import SearchFilterCondition + +__all__ = ["StoreQuestionAnsweringParams", "Filters", "FiltersUnionMember2", "QaOptions"] + + +class StoreQuestionAnsweringParams(TypedDict, total=False): + query: str + """Question to answer. + + If not provided, the question will be extracted from the passed messages. + """ + + store_identifiers: Required[SequenceNotStr[str]] + """IDs or names of stores to search""" + + top_k: int + """Number of results to return""" + + filters: Optional[Filters] + """Optional filter conditions""" + + file_ids: Union[Iterable[object], SequenceNotStr[str], None] + """Optional list of file IDs to filter chunks by (inclusion filter)""" + + search_options: StoreChunkSearchOptionsParam + """Search configuration options""" + + stream: bool + """Whether to stream the answer""" + + qa_options: QaOptions + """Question answering configuration options""" + + +FiltersUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +Filters: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[FiltersUnionMember2]] + + +class QaOptions(TypedDict, total=False): + """Question answering configuration options""" + + cite: bool + """Whether to use citations""" + + multimodal: bool + """Whether to use multimodal context""" + + +from .shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/store_question_answering_response.py b/src/mixedbread/types/store_question_answering_response.py new file mode 100644 index 00000000..dd726f24 --- /dev/null +++ b/src/mixedbread/types/store_question_answering_response.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .scored_text_input_chunk import ScoredTextInputChunk +from .scored_audio_url_input_chunk import ScoredAudioURLInputChunk +from .scored_image_url_input_chunk import ScoredImageURLInputChunk +from .scored_video_url_input_chunk import ScoredVideoURLInputChunk + +__all__ = ["StoreQuestionAnsweringResponse", "Source"] + +Source: TypeAlias = Annotated[ + Union[ScoredTextInputChunk, ScoredImageURLInputChunk, ScoredAudioURLInputChunk, ScoredVideoURLInputChunk], + PropertyInfo(discriminator="type"), +] + + +class StoreQuestionAnsweringResponse(BaseModel): + """Results from a question answering operation.""" + + answer: str + """The answer generated by the LLM""" + + sources: Optional[List[Source]] = None + """Source documents used to generate the answer""" diff --git a/src/mixedbread/types/store_search_params.py b/src/mixedbread/types/store_search_params.py new file mode 100644 index 00000000..7a526d7d --- /dev/null +++ b/src/mixedbread/types/store_search_params.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .extractions.text_input_param import TextInputParam +from .store_chunk_search_options_param import StoreChunkSearchOptionsParam +from .extractions.image_url_input_param import ImageURLInputParam +from .shared_params.search_filter_condition import SearchFilterCondition + +__all__ = ["StoreSearchParams", "Query", "Filters", "FiltersUnionMember2"] + + +class StoreSearchParams(TypedDict, total=False): + query: Required[Query] + """Search query text""" + + store_identifiers: Required[SequenceNotStr[str]] + """IDs or names of stores to search""" + + top_k: int + """Number of results to return""" + + filters: Optional[Filters] + """Optional filter conditions""" + + file_ids: Union[Iterable[object], SequenceNotStr[str], None] + """Optional list of file IDs to filter chunks by (inclusion filter)""" + + search_options: StoreChunkSearchOptionsParam + """Search configuration options""" + + +Query: TypeAlias = Union[str, ImageURLInputParam, TextInputParam] + +FiltersUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +Filters: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[FiltersUnionMember2]] + +from .shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/store_search_response.py b/src/mixedbread/types/store_search_response.py new file mode 100644 index 00000000..6ab757c5 --- /dev/null +++ b/src/mixedbread/types/store_search_response.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .scored_text_input_chunk import ScoredTextInputChunk +from .scored_audio_url_input_chunk import ScoredAudioURLInputChunk +from .scored_image_url_input_chunk import ScoredImageURLInputChunk +from .scored_video_url_input_chunk import ScoredVideoURLInputChunk + +__all__ = ["StoreSearchResponse", "Data"] + +Data: TypeAlias = Annotated[ + Union[ScoredTextInputChunk, ScoredImageURLInputChunk, ScoredAudioURLInputChunk, ScoredVideoURLInputChunk], + PropertyInfo(discriminator="type"), +] + + +class StoreSearchResponse(BaseModel): + object: Optional[Literal["list"]] = None + """The object type of the response""" + + data: List[Data] + """The list of scored store file chunks""" diff --git a/src/mixedbread/types/store_update_params.py b/src/mixedbread/types/store_update_params.py new file mode 100644 index 00000000..e06bbf9d --- /dev/null +++ b/src/mixedbread/types/store_update_params.py @@ -0,0 +1,30 @@ +# 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 + +from .expires_after_param import ExpiresAfterParam + +__all__ = ["StoreUpdateParams"] + + +class StoreUpdateParams(TypedDict, total=False): + name: Optional[str] + """New name for the store. + + Can only contain lowercase letters, numbers, periods (.), and hyphens (-). + """ + + description: Optional[str] + """New description""" + + is_public: Optional[bool] + """Whether the store can be accessed by anyone with valid login credentials""" + + expires_after: Optional[ExpiresAfterParam] + """Represents an expiration policy for a store.""" + + metadata: object + """Optional metadata key-value pairs""" diff --git a/src/mixedbread/types/vector_stores/__init__.py b/src/mixedbread/types/stores/__init__.py similarity index 54% rename from src/mixedbread/types/vector_stores/__init__.py rename to src/mixedbread/types/stores/__init__.py index dfdfd4e4..50862586 100644 --- a/src/mixedbread/types/vector_stores/__init__.py +++ b/src/mixedbread/types/stores/__init__.py @@ -2,10 +2,14 @@ from __future__ import annotations +from .store_file import StoreFile as StoreFile from .file_list_params import FileListParams as FileListParams -from .vector_store_file import VectorStoreFile as VectorStoreFile +from .scored_store_file import ScoredStoreFile as ScoredStoreFile +from .store_file_status import StoreFileStatus as StoreFileStatus from .file_create_params import FileCreateParams as FileCreateParams from .file_list_response import FileListResponse as FileListResponse from .file_search_params import FileSearchParams as FileSearchParams +from .file_update_params import FileUpdateParams as FileUpdateParams +from .file_delete_response import FileDeleteResponse as FileDeleteResponse +from .file_retrieve_params import FileRetrieveParams as FileRetrieveParams from .file_search_response import FileSearchResponse as FileSearchResponse -from .vector_store_file_deleted import VectorStoreFileDeleted as VectorStoreFileDeleted diff --git a/src/mixedbread/types/stores/file_create_params.py b/src/mixedbread/types/stores/file_create_params.py new file mode 100644 index 00000000..c776ea33 --- /dev/null +++ b/src/mixedbread/types/stores/file_create_params.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["FileCreateParams", "Config", "Experimental"] + + +class FileCreateParams(TypedDict, total=False): + metadata: object + """Optional metadata for the file""" + + config: Config + """Configuration for adding the file""" + + external_id: Optional[str] + """External identifier for this file in the store""" + + overwrite: bool + """If true, overwrite an existing file with the same external_id""" + + file_id: Required[str] + """ID of the file to add""" + + experimental: Optional[Experimental] + """Configuration for a file.""" + + +class Config(TypedDict, total=False): + """Configuration for adding the file""" + + parsing_strategy: Literal["fast", "high_quality"] + """Strategy for adding the file, this overrides the store-level default""" + + +class Experimental(TypedDict, total=False): + """Configuration for a file.""" + + parsing_strategy: Literal["fast", "high_quality"] + """Strategy for adding the file, this overrides the store-level default""" diff --git a/src/mixedbread/types/vector_stores/vector_store_file_deleted.py b/src/mixedbread/types/stores/file_delete_response.py similarity index 68% rename from src/mixedbread/types/vector_stores/vector_store_file_deleted.py rename to src/mixedbread/types/stores/file_delete_response.py index 3291c3b8..5788f3f8 100644 --- a/src/mixedbread/types/vector_stores/vector_store_file_deleted.py +++ b/src/mixedbread/types/stores/file_delete_response.py @@ -5,15 +5,17 @@ from ..._models import BaseModel -__all__ = ["VectorStoreFileDeleted"] +__all__ = ["FileDeleteResponse"] -class VectorStoreFileDeleted(BaseModel): +class FileDeleteResponse(BaseModel): + """Response model for file deletion.""" + id: str """ID of the deleted file""" deleted: Optional[bool] = None """Whether the deletion was successful""" - object: Optional[Literal["vector_store.file"]] = None + object: Optional[Literal["store.file"]] = None """Type of the deleted object""" diff --git a/src/mixedbread/types/stores/file_list_params.py b/src/mixedbread/types/stores/file_list_params.py new file mode 100644 index 00000000..2089f9cf --- /dev/null +++ b/src/mixedbread/types/stores/file_list_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable, Optional +from typing_extensions import TypeAlias, TypedDict + +from .store_file_status import StoreFileStatus +from ..shared_params.search_filter_condition import SearchFilterCondition + +__all__ = ["FileListParams", "MetadataFilter", "MetadataFilterUnionMember2"] + + +class FileListParams(TypedDict, total=False): + limit: int + """Maximum number of items to return per page (1-100)""" + + after: Optional[str] + """Cursor for forward pagination - get items after this position. + + Use last_cursor from previous response. + """ + + before: Optional[str] + """Cursor for backward pagination - get items before this position. + + Use first_cursor from previous response. + """ + + include_total: bool + """Whether to include total count in response (expensive operation)""" + + statuses: Optional[List[StoreFileStatus]] + """Status to filter by""" + + metadata_filter: Optional[MetadataFilter] + """Metadata filter to apply to the query""" + + q: Optional[str] + """Search query for fuzzy matching over name and external_id fields""" + + +MetadataFilterUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +MetadataFilter: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[MetadataFilterUnionMember2]] + +from ..shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/stores/file_list_response.py b/src/mixedbread/types/stores/file_list_response.py new file mode 100644 index 00000000..6ddaf8f1 --- /dev/null +++ b/src/mixedbread/types/stores/file_list_response.py @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .store_file import StoreFile + +__all__ = ["FileListResponse", "Pagination"] + + +class Pagination(BaseModel): + """Response model for cursor-based pagination.""" + + has_more: bool + """ + Contextual direction-aware flag: True if more items exist in the requested + pagination direction. For 'after': more items after this page. For 'before': + more items before this page. + """ + + first_cursor: Optional[str] = None + """Cursor of the first item in this page. + + Use for backward pagination. None if page is empty. + """ + + last_cursor: Optional[str] = None + """Cursor of the last item in this page. + + Use for forward pagination. None if page is empty. + """ + + total: Optional[int] = None + """Total number of items available across all pages. + + Only included when include_total=true was requested. Expensive operation - use + sparingly. + """ + + +class FileListResponse(BaseModel): + pagination: Pagination + """Response model for cursor-based pagination.""" + + object: Optional[Literal["list"]] = None + """The object type of the response""" + + data: List[StoreFile] + """The list of store files""" diff --git a/src/mixedbread/types/stores/file_retrieve_params.py b/src/mixedbread/types/stores/file_retrieve_params.py new file mode 100644 index 00000000..b77c870a --- /dev/null +++ b/src/mixedbread/types/stores/file_retrieve_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 Union, Iterable +from typing_extensions import Required, TypedDict + +__all__ = ["FileRetrieveParams"] + + +class FileRetrieveParams(TypedDict, total=False): + store_identifier: Required[str] + """The ID or name of the store""" + + return_chunks: Union[bool, Iterable[int]] + """Whether to return the chunks for the file. + + If a list of integers is provided, only the chunks at the specified indices will + be returned. + """ diff --git a/src/mixedbread/types/stores/file_search_params.py b/src/mixedbread/types/stores/file_search_params.py new file mode 100644 index 00000000..6f6aa2d1 --- /dev/null +++ b/src/mixedbread/types/stores/file_search_params.py @@ -0,0 +1,122 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from ..extractions.text_input_param import TextInputParam +from ..extractions.image_url_input_param import ImageURLInputParam +from ..shared_params.search_filter_condition import SearchFilterCondition + +__all__ = [ + "FileSearchParams", + "Query", + "Filters", + "FiltersUnionMember2", + "SearchOptions", + "SearchOptionsRerank", + "SearchOptionsRerankRerankConfig", + "SearchOptionsAgentic", + "SearchOptionsAgenticAgenticSearchConfig", +] + + +class FileSearchParams(TypedDict, total=False): + query: Required[Query] + """Search query text""" + + store_identifiers: Required[SequenceNotStr[str]] + """IDs or names of stores to search""" + + top_k: int + """Number of results to return""" + + filters: Optional[Filters] + """Optional filter conditions""" + + file_ids: Union[Iterable[object], SequenceNotStr[str], None] + """Optional list of file IDs to filter chunks by (inclusion filter)""" + + search_options: SearchOptions + """Search configuration options""" + + +Query: TypeAlias = Union[str, ImageURLInputParam, TextInputParam] + +FiltersUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +Filters: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[FiltersUnionMember2]] + + +class SearchOptionsRerankRerankConfig(TypedDict, total=False): + """Represents a reranking configuration.""" + + model: str + """The name of the reranking model""" + + with_metadata: Union[bool, SequenceNotStr[str]] + """Whether to include metadata in the reranked results""" + + top_k: Optional[int] + """Maximum number of results to return after reranking. + + If None, returns all reranked results. + """ + + +SearchOptionsRerank: TypeAlias = Union[bool, SearchOptionsRerankRerankConfig] + + +class SearchOptionsAgenticAgenticSearchConfig(TypedDict, total=False): + """Configuration for agentic multi-query search.""" + + max_rounds: int + """Maximum number of search rounds""" + + queries_per_round: int + """Maximum queries per round""" + + +SearchOptionsAgentic: TypeAlias = Union[bool, SearchOptionsAgenticAgenticSearchConfig] + + +class SearchOptions(TypedDict, total=False): + """Search configuration options""" + + score_threshold: float + """Minimum similarity score threshold""" + + rewrite_query: bool + """Whether to rewrite the query. + + Ignored when agentic is enabled (the agent handles query decomposition). + """ + + rerank: Optional[SearchOptionsRerank] + """Whether to rerank results and optional reranking configuration. + + Ignored when agentic is enabled (the agent handles ranking). + """ + + agentic: Optional[SearchOptionsAgentic] + """ + Whether to use agentic multi-query search with automatic query decomposition and + ranking. When enabled, rewrite_query and rerank options are ignored. + """ + + return_metadata: bool + """Whether to return file metadata""" + + return_chunks: bool + """Whether to return matching text chunks""" + + chunks_per_file: int + """Number of chunks to return for each file""" + + apply_search_rules: bool + """Whether to apply search rules""" + + +from ..shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/stores/file_search_response.py b/src/mixedbread/types/stores/file_search_response.py new file mode 100644 index 00000000..304512e4 --- /dev/null +++ b/src/mixedbread/types/stores/file_search_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 typing_extensions import Literal + +from ..._models import BaseModel +from .scored_store_file import ScoredStoreFile + +__all__ = ["FileSearchResponse"] + + +class FileSearchResponse(BaseModel): + object: Optional[Literal["list"]] = None + """The object type of the response""" + + data: List[ScoredStoreFile] + """The list of scored store files""" diff --git a/src/mixedbread/types/stores/file_update_params.py b/src/mixedbread/types/stores/file_update_params.py new file mode 100644 index 00000000..d8dc45e7 --- /dev/null +++ b/src/mixedbread/types/stores/file_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 Dict, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["FileUpdateParams"] + + +class FileUpdateParams(TypedDict, total=False): + store_identifier: Required[str] + """The ID or name of the store""" + + metadata: Optional[Dict[str, object]] + """Updated metadata for the file""" diff --git a/src/mixedbread/types/stores/scored_store_file.py b/src/mixedbread/types/stores/scored_store_file.py new file mode 100644 index 00000000..87483138 --- /dev/null +++ b/src/mixedbread/types/stores/scored_store_file.py @@ -0,0 +1,77 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .store_file_status import StoreFileStatus +from ..scored_text_input_chunk import ScoredTextInputChunk +from ..scored_audio_url_input_chunk import ScoredAudioURLInputChunk +from ..scored_image_url_input_chunk import ScoredImageURLInputChunk +from ..scored_video_url_input_chunk import ScoredVideoURLInputChunk + +__all__ = ["ScoredStoreFile", "Config", "Chunk"] + + +class Config(BaseModel): + """Configuration for a file.""" + + parsing_strategy: Optional[Literal["fast", "high_quality"]] = None + """Strategy for adding the file, this overrides the store-level default""" + + +Chunk: TypeAlias = Annotated[ + Union[ScoredTextInputChunk, ScoredImageURLInputChunk, ScoredAudioURLInputChunk, ScoredVideoURLInputChunk], + PropertyInfo(discriminator="type"), +] + + +class ScoredStoreFile(BaseModel): + """Represents a scored store file.""" + + id: str + """Unique identifier for the file""" + + filename: Optional[str] = None + """Name of the file""" + + metadata: Optional[object] = None + """Optional file metadata""" + + external_id: Optional[str] = None + """External identifier for this file in the store""" + + status: Optional[StoreFileStatus] = None + """Processing status of the file""" + + last_error: Optional[object] = None + """Last error message if processing failed""" + + store_id: str + """ID of the containing store""" + + created_at: datetime + """Timestamp of store file creation""" + + version: Optional[int] = None + """Version number of the file""" + + usage_bytes: Optional[int] = None + """Storage usage in bytes""" + + usage_tokens: Optional[int] = None + """Storage usage in tokens""" + + config: Optional[Config] = None + """Configuration for a file.""" + + object: Optional[Literal["store.file"]] = None + """Type of the object""" + + chunks: Optional[List[Chunk]] = None + """Array of scored file chunks""" + + score: float + """score of the file""" diff --git a/src/mixedbread/types/stores/store_file.py b/src/mixedbread/types/stores/store_file.py new file mode 100644 index 00000000..0fe097ba --- /dev/null +++ b/src/mixedbread/types/stores/store_file.py @@ -0,0 +1,1215 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from pydantic import Field as FieldInfo + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .store_file_status import StoreFileStatus + +__all__ = [ + "StoreFile", + "Config", + "Chunk", + "ChunkTextInputChunk", + "ChunkTextInputChunkGeneratedMetadata", + "ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata", + "ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading", + "ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext", + "ChunkTextInputChunkGeneratedMetadataTextChunkGeneratedMetadata", + "ChunkTextInputChunkGeneratedMetadataPdfChunkGeneratedMetadata", + "ChunkTextInputChunkGeneratedMetadataCodeChunkGeneratedMetadata", + "ChunkTextInputChunkGeneratedMetadataAudioChunkGeneratedMetadata", + "ChunkTextInputChunkGeneratedMetadataVideoChunkGeneratedMetadata", + "ChunkTextInputChunkGeneratedMetadataImageChunkGeneratedMetadata", + "ChunkImageURLInputChunk", + "ChunkImageURLInputChunkGeneratedMetadata", + "ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata", + "ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading", + "ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext", + "ChunkImageURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata", + "ChunkImageURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata", + "ChunkImageURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata", + "ChunkImageURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata", + "ChunkImageURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata", + "ChunkImageURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata", + "ChunkImageURLInputChunkImageURL", + "ChunkAudioURLInputChunk", + "ChunkAudioURLInputChunkGeneratedMetadata", + "ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata", + "ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading", + "ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext", + "ChunkAudioURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata", + "ChunkAudioURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata", + "ChunkAudioURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata", + "ChunkAudioURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata", + "ChunkAudioURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata", + "ChunkAudioURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata", + "ChunkAudioURLInputChunkAudioURL", + "ChunkVideoURLInputChunk", + "ChunkVideoURLInputChunkGeneratedMetadata", + "ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata", + "ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading", + "ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext", + "ChunkVideoURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata", + "ChunkVideoURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata", + "ChunkVideoURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata", + "ChunkVideoURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata", + "ChunkVideoURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata", + "ChunkVideoURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata", + "ChunkVideoURLInputChunkVideoURL", +] + + +class Config(BaseModel): + """Configuration for a file.""" + + parsing_strategy: Optional[Literal["fast", "high_quality"]] = None + """Strategy for adding the file, this overrides the store-level default""" + + +class ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading(BaseModel): + level: int + + text: str + + +class ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext(BaseModel): + level: int + + text: str + + +class ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["markdown"]] = None + + file_type: Optional[Literal["text/markdown"]] = None + + language: str + + word_count: int + + file_size: int + + chunk_headings: Optional[List[ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading]] = ( + None + ) + + heading_context: Optional[ + List[ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext] + ] = None + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + frontmatter: Optional[Dict[str, object]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkTextInputChunkGeneratedMetadataTextChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["text"]] = None + + file_type: Optional[Literal["text/plain"]] = None + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkTextInputChunkGeneratedMetadataPdfChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["pdf"]] = None + + file_type: Optional[Literal["application/pdf"]] = None + + total_pages: int + + total_size: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkTextInputChunkGeneratedMetadataCodeChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["code"]] = None + + file_type: str + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkTextInputChunkGeneratedMetadataAudioChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["audio"]] = None + + file_type: str + + file_size: int + + total_duration_seconds: float + + sample_rate: int + + channels: int + + audio_format: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkTextInputChunkGeneratedMetadataVideoChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["video"]] = None + + file_type: str + + file_size: Optional[int] = None + + total_duration_seconds: float + + fps: float + + width: int + + height: int + + frame_count: int + + has_audio_stream: Optional[bool] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkTextInputChunkGeneratedMetadataImageChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["image"]] = None + + file_type: str + + file_size: int + + width: int + + height: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +ChunkTextInputChunkGeneratedMetadata: TypeAlias = Annotated[ + Union[ + ChunkTextInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata, + ChunkTextInputChunkGeneratedMetadataTextChunkGeneratedMetadata, + ChunkTextInputChunkGeneratedMetadataPdfChunkGeneratedMetadata, + ChunkTextInputChunkGeneratedMetadataCodeChunkGeneratedMetadata, + ChunkTextInputChunkGeneratedMetadataAudioChunkGeneratedMetadata, + ChunkTextInputChunkGeneratedMetadataVideoChunkGeneratedMetadata, + ChunkTextInputChunkGeneratedMetadataImageChunkGeneratedMetadata, + None, + ], + PropertyInfo(discriminator="type"), +] + + +class ChunkTextInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[ChunkTextInputChunkGeneratedMetadata] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + type: Optional[Literal["text"]] = None + """Input type identifier""" + + offset: Optional[int] = None + """The offset of the text in the file relative to the start of the file.""" + + text: Optional[str] = None + """Text content""" + + +class ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading(BaseModel): + level: int + + text: str + + +class ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext(BaseModel): + level: int + + text: str + + +class ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["markdown"]] = None + + file_type: Optional[Literal["text/markdown"]] = None + + language: str + + word_count: int + + file_size: int + + chunk_headings: Optional[ + List[ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading] + ] = None + + heading_context: Optional[ + List[ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext] + ] = None + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + frontmatter: Optional[Dict[str, object]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkImageURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["text"]] = None + + file_type: Optional[Literal["text/plain"]] = None + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkImageURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["pdf"]] = None + + file_type: Optional[Literal["application/pdf"]] = None + + total_pages: int + + total_size: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkImageURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["code"]] = None + + file_type: str + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkImageURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["audio"]] = None + + file_type: str + + file_size: int + + total_duration_seconds: float + + sample_rate: int + + channels: int + + audio_format: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkImageURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["video"]] = None + + file_type: str + + file_size: Optional[int] = None + + total_duration_seconds: float + + fps: float + + width: int + + height: int + + frame_count: int + + has_audio_stream: Optional[bool] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkImageURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["image"]] = None + + file_type: str + + file_size: int + + width: int + + height: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +ChunkImageURLInputChunkGeneratedMetadata: TypeAlias = Annotated[ + Union[ + ChunkImageURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata, + ChunkImageURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata, + ChunkImageURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata, + ChunkImageURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata, + ChunkImageURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata, + ChunkImageURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata, + ChunkImageURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata, + None, + ], + PropertyInfo(discriminator="type"), +] + + +class ChunkImageURLInputChunkImageURL(BaseModel): + """Model for image URL validation.""" + + url: str + """The image URL. Can be either a URL or a Data URI.""" + + format: Optional[str] = None + """The image format/mimetype""" + + +class ChunkImageURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[ChunkImageURLInputChunkGeneratedMetadata] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + type: Optional[Literal["image_url"]] = None + """Input type identifier""" + + ocr_text: Optional[str] = None + """ocr text of the image""" + + summary: Optional[str] = None + """summary of the image""" + + image_url: Optional[ChunkImageURLInputChunkImageURL] = None + """Model for image URL validation.""" + + +class ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading(BaseModel): + level: int + + text: str + + +class ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext(BaseModel): + level: int + + text: str + + +class ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["markdown"]] = None + + file_type: Optional[Literal["text/markdown"]] = None + + language: str + + word_count: int + + file_size: int + + chunk_headings: Optional[ + List[ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading] + ] = None + + heading_context: Optional[ + List[ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext] + ] = None + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + frontmatter: Optional[Dict[str, object]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkAudioURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["text"]] = None + + file_type: Optional[Literal["text/plain"]] = None + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkAudioURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["pdf"]] = None + + file_type: Optional[Literal["application/pdf"]] = None + + total_pages: int + + total_size: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkAudioURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["code"]] = None + + file_type: str + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkAudioURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["audio"]] = None + + file_type: str + + file_size: int + + total_duration_seconds: float + + sample_rate: int + + channels: int + + audio_format: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkAudioURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["video"]] = None + + file_type: str + + file_size: Optional[int] = None + + total_duration_seconds: float + + fps: float + + width: int + + height: int + + frame_count: int + + has_audio_stream: Optional[bool] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkAudioURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["image"]] = None + + file_type: str + + file_size: int + + width: int + + height: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +ChunkAudioURLInputChunkGeneratedMetadata: TypeAlias = Annotated[ + Union[ + ChunkAudioURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata, + ChunkAudioURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata, + ChunkAudioURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata, + ChunkAudioURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata, + ChunkAudioURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata, + ChunkAudioURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata, + ChunkAudioURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata, + None, + ], + PropertyInfo(discriminator="type"), +] + + +class ChunkAudioURLInputChunkAudioURL(BaseModel): + """Model for audio URL validation.""" + + url: str + """The audio URL. Can be either a URL or a Data URI.""" + + +class ChunkAudioURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[ChunkAudioURLInputChunkGeneratedMetadata] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + type: Optional[Literal["audio_url"]] = None + """Input type identifier""" + + transcription: Optional[str] = None + """speech recognition (sr) text of the audio""" + + summary: Optional[str] = None + """summary of the audio""" + + audio_url: Optional[ChunkAudioURLInputChunkAudioURL] = None + """Model for audio URL validation.""" + + sampling_rate: int + """The sampling rate of the audio.""" + + +class ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading(BaseModel): + level: int + + text: str + + +class ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext(BaseModel): + level: int + + text: str + + +class ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["markdown"]] = None + + file_type: Optional[Literal["text/markdown"]] = None + + language: str + + word_count: int + + file_size: int + + chunk_headings: Optional[ + List[ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataChunkHeading] + ] = None + + heading_context: Optional[ + List[ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadataHeadingContext] + ] = None + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + frontmatter: Optional[Dict[str, object]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkVideoURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["text"]] = None + + file_type: Optional[Literal["text/plain"]] = None + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkVideoURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["pdf"]] = None + + file_type: Optional[Literal["application/pdf"]] = None + + total_pages: int + + total_size: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkVideoURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["code"]] = None + + file_type: str + + language: str + + word_count: int + + file_size: int + + start_line: Optional[int] = None + + num_lines: Optional[int] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkVideoURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["audio"]] = None + + file_type: str + + file_size: int + + total_duration_seconds: float + + sample_rate: int + + channels: int + + audio_format: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkVideoURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["video"]] = None + + file_type: str + + file_size: Optional[int] = None + + total_duration_seconds: float + + fps: float + + width: int + + height: int + + frame_count: int + + has_audio_stream: Optional[bool] = None + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +class ChunkVideoURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata(BaseModel): + type: Optional[Literal["image"]] = None + + file_type: str + + file_size: int + + width: int + + height: int + + file_extension: Optional[str] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] + + +ChunkVideoURLInputChunkGeneratedMetadata: TypeAlias = Annotated[ + Union[ + ChunkVideoURLInputChunkGeneratedMetadataMarkdownChunkGeneratedMetadata, + ChunkVideoURLInputChunkGeneratedMetadataTextChunkGeneratedMetadata, + ChunkVideoURLInputChunkGeneratedMetadataPdfChunkGeneratedMetadata, + ChunkVideoURLInputChunkGeneratedMetadataCodeChunkGeneratedMetadata, + ChunkVideoURLInputChunkGeneratedMetadataAudioChunkGeneratedMetadata, + ChunkVideoURLInputChunkGeneratedMetadataVideoChunkGeneratedMetadata, + ChunkVideoURLInputChunkGeneratedMetadataImageChunkGeneratedMetadata, + None, + ], + PropertyInfo(discriminator="type"), +] + + +class ChunkVideoURLInputChunkVideoURL(BaseModel): + """Model for video URL validation.""" + + url: str + """The video URL. Can be either a URL or a Data URI.""" + + +class ChunkVideoURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[ChunkVideoURLInputChunkGeneratedMetadata] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + type: Optional[Literal["video_url"]] = None + """Input type identifier""" + + transcription: Optional[str] = None + """speech recognition (sr) text of the video""" + + summary: Optional[str] = None + """summary of the video""" + + video_url: Optional[ChunkVideoURLInputChunkVideoURL] = None + """Model for video URL validation.""" + + +Chunk: TypeAlias = Annotated[ + Union[ChunkTextInputChunk, ChunkImageURLInputChunk, ChunkAudioURLInputChunk, ChunkVideoURLInputChunk], + PropertyInfo(discriminator="type"), +] + + +class StoreFile(BaseModel): + """Represents a file stored in a store.""" + + id: str + """Unique identifier for the file""" + + filename: Optional[str] = None + """Name of the file""" + + metadata: Optional[object] = None + """Optional file metadata""" + + external_id: Optional[str] = None + """External identifier for this file in the store""" + + status: Optional[StoreFileStatus] = None + """Processing status of the file""" + + last_error: Optional[object] = None + """Last error message if processing failed""" + + store_id: str + """ID of the containing store""" + + created_at: datetime + """Timestamp of store file creation""" + + version: Optional[int] = None + """Version number of the file""" + + usage_bytes: Optional[int] = None + """Storage usage in bytes""" + + usage_tokens: Optional[int] = None + """Storage usage in tokens""" + + config: Optional[Config] = None + """Configuration for a file.""" + + object: Optional[Literal["store.file"]] = None + """Type of the object""" + + chunks: Optional[List[Chunk]] = None + """chunks""" diff --git a/src/mixedbread/types/stores/store_file_status.py b/src/mixedbread/types/stores/store_file_status.py new file mode 100644 index 00000000..a0061477 --- /dev/null +++ b/src/mixedbread/types/stores/store_file_status.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["StoreFileStatus"] + +StoreFileStatus: TypeAlias = Literal["pending", "in_progress", "cancelled", "completed", "failed"] diff --git a/src/mixedbread/types/vector_store.py b/src/mixedbread/types/vector_store.py deleted file mode 100644 index 262d13c3..00000000 --- a/src/mixedbread/types/vector_store.py +++ /dev/null @@ -1,69 +0,0 @@ -# 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__ = ["VectorStore", "ExpiresAfter", "FileCounts"] - - -class ExpiresAfter(BaseModel): - anchor: Optional[Literal["last_used_at"]] = None - """Anchor date for the expiration policy""" - - days: Optional[int] = None - """Number of days after which the vector store expires""" - - -class FileCounts(BaseModel): - canceled: Optional[int] = None - """Number of files whose processing was canceled""" - - failed: Optional[int] = None - """Number of files that failed processing""" - - in_progress: Optional[int] = None - """Number of files currently being processed""" - - successful: Optional[int] = None - """Number of successfully processed files""" - - total: Optional[int] = None - """Total number of files""" - - -class VectorStore(BaseModel): - id: str - """Unique identifier for the vector store""" - - created_at: datetime - """Timestamp when the vector store was created""" - - name: str - """Name of the vector store""" - - updated_at: datetime - """Timestamp when the vector store was last updated""" - - description: Optional[str] = None - """Detailed description of the vector store's purpose and contents""" - - expires_after: Optional[ExpiresAfter] = None - """Represents an expiration policy for a vector store.""" - - expires_at: Optional[datetime] = None - """Optional expiration timestamp for the vector store""" - - file_counts: Optional[FileCounts] = None - """Counts of files in different states""" - - last_active_at: Optional[datetime] = None - """Timestamp when the vector store was last used""" - - metadata: Optional[object] = None - """Additional metadata associated with the vector store""" - - object: Optional[Literal["vector_store"]] = None - """Type of the object""" diff --git a/src/mixedbread/types/vector_store_create_params.py b/src/mixedbread/types/vector_store_create_params.py deleted file mode 100644 index 891e58c9..00000000 --- a/src/mixedbread/types/vector_store_create_params.py +++ /dev/null @@ -1,33 +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__ = ["VectorStoreCreateParams", "ExpiresAfter"] - - -class VectorStoreCreateParams(TypedDict, total=False): - description: Optional[str] - """Description of the vector store""" - - expires_after: Optional[ExpiresAfter] - """Represents an expiration policy for a vector store.""" - - file_ids: Optional[List[str]] - """Optional list of file IDs""" - - metadata: object - """Optional metadata key-value pairs""" - - name: Optional[str] - """Name for the new vector store""" - - -class ExpiresAfter(TypedDict, total=False): - anchor: Literal["last_used_at"] - """Anchor date for the expiration policy""" - - days: int - """Number of days after which the vector store expires""" diff --git a/src/mixedbread/types/vector_store_list_params.py b/src/mixedbread/types/vector_store_list_params.py deleted file mode 100644 index 39225421..00000000 --- a/src/mixedbread/types/vector_store_list_params.py +++ /dev/null @@ -1,15 +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__ = ["VectorStoreListParams"] - - -class VectorStoreListParams(TypedDict, total=False): - limit: int - """Maximum number of items to return per page""" - - offset: int - """Offset of the first item to return""" diff --git a/src/mixedbread/types/vector_store_list_response.py b/src/mixedbread/types/vector_store_list_response.py deleted file mode 100644 index 0b1f21c2..00000000 --- a/src/mixedbread/types/vector_store_list_response.py +++ /dev/null @@ -1,31 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from .._models import BaseModel -from .vector_store import VectorStore - -__all__ = ["VectorStoreListResponse", "Pagination"] - - -class Pagination(BaseModel): - limit: Optional[int] = None - """Maximum number of items to return per page""" - - offset: Optional[int] = None - """Offset of the first item to return""" - - total: Optional[int] = None - """Total number of items available""" - - -class VectorStoreListResponse(BaseModel): - data: List[VectorStore] - """The list of vector stores""" - - pagination: Pagination - """Pagination model that includes total count of items.""" - - object: Optional[Literal["list"]] = None - """The object type of the response""" diff --git a/src/mixedbread/types/vector_store_question_answering_params.py b/src/mixedbread/types/vector_store_question_answering_params.py deleted file mode 100644 index 1d901395..00000000 --- a/src/mixedbread/types/vector_store_question_answering_params.py +++ /dev/null @@ -1,50 +0,0 @@ -# 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 Required, TypedDict - -__all__ = ["VectorStoreQuestionAnsweringParams", "QaOptions", "SearchOptions"] - - -class VectorStoreQuestionAnsweringParams(TypedDict, total=False): - vector_store_ids: Required[List[str]] - """IDs of vector stores to search""" - - qa_options: QaOptions - """Question answering configuration options""" - - query: str - """Question to answer. - - If not provided, the question will be extracted from the passed messages. - """ - - search_options: SearchOptions - """Search configuration options""" - - stream: bool - """Whether to stream the answer""" - - top_k: int - """Number of results to return""" - - -class QaOptions(TypedDict, total=False): - cite: bool - """Whether to use citations""" - - -class SearchOptions(TypedDict, total=False): - return_chunks: bool - """Whether to return matching text chunks""" - - return_metadata: bool - """Whether to return file metadata""" - - rewrite_query: bool - """Whether to rewrite the query""" - - score_threshold: float - """Minimum similarity score threshold""" diff --git a/src/mixedbread/types/vector_store_search_params.py b/src/mixedbread/types/vector_store_search_params.py deleted file mode 100644 index 90900d4a..00000000 --- a/src/mixedbread/types/vector_store_search_params.py +++ /dev/null @@ -1,36 +0,0 @@ -# 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 Required, TypedDict - -__all__ = ["VectorStoreSearchParams", "SearchOptions"] - - -class VectorStoreSearchParams(TypedDict, total=False): - query: Required[str] - """Search query text""" - - vector_store_ids: Required[List[str]] - """IDs of vector stores to search""" - - search_options: SearchOptions - """Search configuration options""" - - top_k: int - """Number of results to return""" - - -class SearchOptions(TypedDict, total=False): - return_chunks: bool - """Whether to return matching text chunks""" - - return_metadata: bool - """Whether to return file metadata""" - - rewrite_query: bool - """Whether to rewrite the query""" - - score_threshold: float - """Minimum similarity score threshold""" diff --git a/src/mixedbread/types/vector_store_search_response.py b/src/mixedbread/types/vector_store_search_response.py deleted file mode 100644 index 53d32217..00000000 --- a/src/mixedbread/types/vector_store_search_response.py +++ /dev/null @@ -1,70 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Union, Optional -from typing_extensions import Literal, TypeAlias - -from .._models import BaseModel - -__all__ = [ - "VectorStoreSearchResponse", - "Data", - "DataValue", - "DataValueImageURLInput", - "DataValueImageURLInputImage", - "DataValueTextInput", -] - - -class DataValueImageURLInputImage(BaseModel): - url: str - """The image URL. Can be either a URL or a Data URI.""" - - -class DataValueImageURLInput(BaseModel): - image: DataValueImageURLInputImage - """The image input specification.""" - - type: Optional[Literal["image_url"]] = None - """Input type identifier""" - - -class DataValueTextInput(BaseModel): - text: str - """Text content to process""" - - type: Optional[Literal["text"]] = None - """Input type identifier""" - - -DataValue: TypeAlias = Union[str, DataValueImageURLInput, DataValueTextInput, Dict[str, object], None] - - -class Data(BaseModel): - file_id: str - """file id""" - - position: int - """position of the chunk in a file""" - - score: float - """score of the chunk""" - - vector_store_id: str - """vector store id""" - - content: Optional[str] = None - """content of the chunk""" - - metadata: Optional[object] = None - """file metadata""" - - value: Optional[DataValue] = None - """value of the chunk""" - - -class VectorStoreSearchResponse(BaseModel): - data: List[Data] - """The list of scored vector store file chunks""" - - object: Optional[Literal["list"]] = None - """The object type of the response""" diff --git a/src/mixedbread/types/vector_store_update_params.py b/src/mixedbread/types/vector_store_update_params.py deleted file mode 100644 index 74008480..00000000 --- a/src/mixedbread/types/vector_store_update_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 import Optional -from typing_extensions import Literal, TypedDict - -__all__ = ["VectorStoreUpdateParams", "ExpiresAfter"] - - -class VectorStoreUpdateParams(TypedDict, total=False): - description: Optional[str] - """New description""" - - expires_after: Optional[ExpiresAfter] - """Represents an expiration policy for a vector store.""" - - metadata: object - """Optional metadata key-value pairs""" - - name: Optional[str] - """New name for the vector store""" - - -class ExpiresAfter(TypedDict, total=False): - anchor: Literal["last_used_at"] - """Anchor date for the expiration policy""" - - days: int - """Number of days after which the vector store expires""" diff --git a/src/mixedbread/types/vector_stores/file_create_params.py b/src/mixedbread/types/vector_stores/file_create_params.py deleted file mode 100644 index 930996a7..00000000 --- a/src/mixedbread/types/vector_stores/file_create_params.py +++ /dev/null @@ -1,15 +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__ = ["FileCreateParams"] - - -class FileCreateParams(TypedDict, total=False): - file_id: Required[str] - """ID of the file to add""" - - metadata: object - """Optional metadata for the file""" diff --git a/src/mixedbread/types/vector_stores/file_list_response.py b/src/mixedbread/types/vector_stores/file_list_response.py deleted file mode 100644 index aec81cf9..00000000 --- a/src/mixedbread/types/vector_stores/file_list_response.py +++ /dev/null @@ -1,31 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from typing_extensions import Literal - -from ..._models import BaseModel -from .vector_store_file import VectorStoreFile - -__all__ = ["FileListResponse", "Pagination"] - - -class Pagination(BaseModel): - limit: Optional[int] = None - """Maximum number of items to return per page""" - - offset: Optional[int] = None - """Offset of the first item to return""" - - total: Optional[int] = None - """Total number of items available""" - - -class FileListResponse(BaseModel): - data: List[VectorStoreFile] - """The list of vector store files""" - - pagination: Pagination - """Pagination model that includes total count of items.""" - - object: Optional[Literal["list"]] = None - """The object type of the response""" diff --git a/src/mixedbread/types/vector_stores/file_search_params.py b/src/mixedbread/types/vector_stores/file_search_params.py deleted file mode 100644 index f98e3f10..00000000 --- a/src/mixedbread/types/vector_stores/file_search_params.py +++ /dev/null @@ -1,36 +0,0 @@ -# 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 Required, TypedDict - -__all__ = ["FileSearchParams", "SearchOptions"] - - -class FileSearchParams(TypedDict, total=False): - query: Required[str] - """Search query text""" - - vector_store_ids: Required[List[str]] - """IDs of vector stores to search""" - - search_options: SearchOptions - """Search configuration options""" - - top_k: int - """Number of results to return""" - - -class SearchOptions(TypedDict, total=False): - return_chunks: bool - """Whether to return matching text chunks""" - - return_metadata: bool - """Whether to return file metadata""" - - rewrite_query: bool - """Whether to rewrite the query""" - - score_threshold: float - """Minimum similarity score threshold""" diff --git a/src/mixedbread/types/vector_stores/file_search_response.py b/src/mixedbread/types/vector_stores/file_search_response.py deleted file mode 100644 index 57ee464f..00000000 --- a/src/mixedbread/types/vector_stores/file_search_response.py +++ /dev/null @@ -1,98 +0,0 @@ -# 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, TypeAlias - -from ..._models import BaseModel - -__all__ = [ - "FileSearchResponse", - "Data", - "DataChunk", - "DataChunkValue", - "DataChunkValueImageURLInput", - "DataChunkValueImageURLInputImage", - "DataChunkValueTextInput", -] - - -class DataChunkValueImageURLInputImage(BaseModel): - url: str - """The image URL. Can be either a URL or a Data URI.""" - - -class DataChunkValueImageURLInput(BaseModel): - image: DataChunkValueImageURLInputImage - """The image input specification.""" - - type: Optional[Literal["image_url"]] = None - """Input type identifier""" - - -class DataChunkValueTextInput(BaseModel): - text: str - """Text content to process""" - - type: Optional[Literal["text"]] = None - """Input type identifier""" - - -DataChunkValue: TypeAlias = Union[str, DataChunkValueImageURLInput, DataChunkValueTextInput, Dict[str, object], None] - - -class DataChunk(BaseModel): - file_id: str - """file id""" - - position: int - """position of the chunk in a file""" - - score: float - """score of the chunk""" - - vector_store_id: str - """vector store id""" - - content: Optional[str] = None - """content of the chunk""" - - metadata: Optional[object] = None - """file metadata""" - - value: Optional[DataChunkValue] = None - """value of the chunk""" - - -class Data(BaseModel): - id: str - """file id""" - - created_at: datetime - """Timestamp of vector store file creation""" - - score: float - """score of the file""" - - usage_bytes: int - """usage in bytes""" - - vector_store_id: str - """vector store id""" - - version: int - """version of the file""" - - chunks: Optional[List[DataChunk]] = None - """chunks""" - - metadata: Optional[object] = None - """metadata""" - - -class FileSearchResponse(BaseModel): - data: List[Data] - """The list of scored vector store files""" - - object: Optional[Literal["list"]] = None - """The object type of the response""" diff --git a/src/mixedbread/types/vector_stores/vector_store_file.py b/src/mixedbread/types/vector_stores/vector_store_file.py deleted file mode 100644 index 996333bc..00000000 --- a/src/mixedbread/types/vector_stores/vector_store_file.py +++ /dev/null @@ -1,46 +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 -from ..file_object import FileObject - -__all__ = ["VectorStoreFile"] - - -class VectorStoreFile(BaseModel): - id: str - """Unique identifier for the file""" - - created_at: datetime - """Timestamp of vector store file creation""" - - vector_store_id: str - """ID of the containing vector store""" - - errors: Optional[List[str]] = None - """List of error messages if processing failed""" - - file_object: Optional[FileObject] = None - """A model representing a file object in the system. - - This model contains metadata about files stored in the system, including - identifiers, size information, and timestamps. - """ - - metadata: Optional[object] = None - """Optional file metadata""" - - object: Optional[Literal["vector_store.file"]] = None - """Type of the object""" - - status: Optional[Literal["none", "running", "canceled", "successful", "failed", "resumable", "pending"]] = None - """Processing status of the file""" - - usage_bytes: Optional[int] = None - """Storage usage in bytes""" - - version: Optional[int] = None - """Version number of the file""" diff --git a/tests/api_resources/vector_stores/__init__.py b/tests/api_resources/data_sources/__init__.py similarity index 100% rename from tests/api_resources/vector_stores/__init__.py rename to tests/api_resources/data_sources/__init__.py diff --git a/tests/api_resources/data_sources/test_connectors.py b/tests/api_resources/data_sources/test_connectors.py new file mode 100644 index 00000000..20d3847e --- /dev/null +++ b/tests/api_resources/data_sources/test_connectors.py @@ -0,0 +1,546 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.pagination import SyncCursor, AsyncCursor +from mixedbread.types.data_sources import ( + DataSourceConnector, + ConnectorDeleteResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestConnectors: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Mixedbread) -> None: + connector = client.data_sources.connectors.create( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + store_id="store_id", + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Mixedbread) -> None: + connector = client.data_sources.connectors.create( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + store_id="store_id", + name="name", + trigger_sync=True, + metadata={}, + polling_interval=1800, + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Mixedbread) -> None: + response = client.data_sources.connectors.with_raw_response.create( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + store_id="store_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Mixedbread) -> None: + with client.data_sources.connectors.with_streaming_response.create( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + store_id="store_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.connectors.with_raw_response.create( + data_source_id="", + store_id="store_id", + ) + + @parametrize + def test_method_retrieve(self, client: Mixedbread) -> None: + connector = client.data_sources.connectors.retrieve( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Mixedbread) -> None: + response = client.data_sources.connectors.with_raw_response.retrieve( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Mixedbread) -> None: + with client.data_sources.connectors.with_streaming_response.retrieve( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.connectors.with_raw_response.retrieve( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connector_id` but received ''"): + client.data_sources.connectors.with_raw_response.retrieve( + connector_id="", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + def test_method_update(self, client: Mixedbread) -> None: + connector = client.data_sources.connectors.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Mixedbread) -> None: + connector = client.data_sources.connectors.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + metadata={"foo": "bar"}, + trigger_sync=True, + polling_interval=1800, + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Mixedbread) -> None: + response = client.data_sources.connectors.with_raw_response.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Mixedbread) -> None: + with client.data_sources.connectors.with_streaming_response.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.connectors.with_raw_response.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connector_id` but received ''"): + client.data_sources.connectors.with_raw_response.update( + connector_id="", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + connector = client.data_sources.connectors.list( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncCursor[DataSourceConnector], connector, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Mixedbread) -> None: + connector = client.data_sources.connectors.list( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + ) + assert_matches_type(SyncCursor[DataSourceConnector], connector, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.data_sources.connectors.with_raw_response.list( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = response.parse() + assert_matches_type(SyncCursor[DataSourceConnector], connector, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.data_sources.connectors.with_streaming_response.list( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = response.parse() + assert_matches_type(SyncCursor[DataSourceConnector], connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.connectors.with_raw_response.list( + data_source_id="", + ) + + @parametrize + def test_method_delete(self, client: Mixedbread) -> None: + connector = client.data_sources.connectors.delete( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ConnectorDeleteResponse, connector, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Mixedbread) -> None: + response = client.data_sources.connectors.with_raw_response.delete( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = response.parse() + assert_matches_type(ConnectorDeleteResponse, connector, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Mixedbread) -> None: + with client.data_sources.connectors.with_streaming_response.delete( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = response.parse() + assert_matches_type(ConnectorDeleteResponse, connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.connectors.with_raw_response.delete( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connector_id` but received ''"): + client.data_sources.connectors.with_raw_response.delete( + connector_id="", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncConnectors: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncMixedbread) -> None: + connector = await async_client.data_sources.connectors.create( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + store_id="store_id", + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: + connector = await async_client.data_sources.connectors.create( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + store_id="store_id", + name="name", + trigger_sync=True, + metadata={}, + polling_interval=1800, + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.connectors.with_raw_response.create( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + store_id="store_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = await response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.connectors.with_streaming_response.create( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + store_id="store_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = await response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.connectors.with_raw_response.create( + data_source_id="", + store_id="store_id", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: + connector = await async_client.data_sources.connectors.retrieve( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.connectors.with_raw_response.retrieve( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = await response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.connectors.with_streaming_response.retrieve( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = await response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.connectors.with_raw_response.retrieve( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connector_id` but received ''"): + await async_client.data_sources.connectors.with_raw_response.retrieve( + connector_id="", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncMixedbread) -> None: + connector = await async_client.data_sources.connectors.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncMixedbread) -> None: + connector = await async_client.data_sources.connectors.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + metadata={"foo": "bar"}, + trigger_sync=True, + polling_interval=1800, + ) + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.connectors.with_raw_response.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = await response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.connectors.with_streaming_response.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = await response.parse() + assert_matches_type(DataSourceConnector, connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.connectors.with_raw_response.update( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connector_id` but received ''"): + await async_client.data_sources.connectors.with_raw_response.update( + connector_id="", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + connector = await async_client.data_sources.connectors.list( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncCursor[DataSourceConnector], connector, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: + connector = await async_client.data_sources.connectors.list( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + ) + assert_matches_type(AsyncCursor[DataSourceConnector], connector, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.connectors.with_raw_response.list( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = await response.parse() + assert_matches_type(AsyncCursor[DataSourceConnector], connector, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.connectors.with_streaming_response.list( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = await response.parse() + assert_matches_type(AsyncCursor[DataSourceConnector], connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.connectors.with_raw_response.list( + data_source_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncMixedbread) -> None: + connector = await async_client.data_sources.connectors.delete( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ConnectorDeleteResponse, connector, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.connectors.with_raw_response.delete( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connector = await response.parse() + assert_matches_type(ConnectorDeleteResponse, connector, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.connectors.with_streaming_response.delete( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connector = await response.parse() + assert_matches_type(ConnectorDeleteResponse, connector, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.connectors.with_raw_response.delete( + connector_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + data_source_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connector_id` but received ''"): + await async_client.data_sources.connectors.with_raw_response.delete( + connector_id="", + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/extractions/test_content.py b/tests/api_resources/extractions/test_content.py index 7beb468b..63ee1aed 100644 --- a/tests/api_resources/extractions/test_content.py +++ b/tests/api_resources/extractions/test_content.py @@ -20,16 +20,25 @@ class TestContent: @parametrize def test_method_create(self, client: Mixedbread) -> None: content = client.extractions.content.create( - content="content", - json_schema={}, + content="string", + json_schema={"foo": "bar"}, + ) + assert_matches_type(ExtractionResult, content, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Mixedbread) -> None: + content = client.extractions.content.create( + content="string", + json_schema={"foo": "bar"}, + instructions="instructions", ) assert_matches_type(ExtractionResult, content, path=["response"]) @parametrize def test_raw_response_create(self, client: Mixedbread) -> None: response = client.extractions.content.with_raw_response.create( - content="content", - json_schema={}, + content="string", + json_schema={"foo": "bar"}, ) assert response.is_closed is True @@ -40,8 +49,8 @@ def test_raw_response_create(self, client: Mixedbread) -> None: @parametrize def test_streaming_response_create(self, client: Mixedbread) -> None: with client.extractions.content.with_streaming_response.create( - content="content", - json_schema={}, + content="string", + json_schema={"foo": "bar"}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -53,21 +62,32 @@ def test_streaming_response_create(self, client: Mixedbread) -> None: class TestAsyncContent: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncMixedbread) -> None: content = await async_client.extractions.content.create( - content="content", - json_schema={}, + content="string", + json_schema={"foo": "bar"}, + ) + assert_matches_type(ExtractionResult, content, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: + content = await async_client.extractions.content.create( + content="string", + json_schema={"foo": "bar"}, + instructions="instructions", ) assert_matches_type(ExtractionResult, content, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: response = await async_client.extractions.content.with_raw_response.create( - content="content", - json_schema={}, + content="string", + json_schema={"foo": "bar"}, ) assert response.is_closed is True @@ -78,8 +98,8 @@ async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: async with async_client.extractions.content.with_streaming_response.create( - content="content", - json_schema={}, + content="string", + json_schema={"foo": "bar"}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/extractions/test_jobs.py b/tests/api_resources/extractions/test_jobs.py index bb055ba7..b79a66a4 100644 --- a/tests/api_resources/extractions/test_jobs.py +++ b/tests/api_resources/extractions/test_jobs.py @@ -21,7 +21,7 @@ class TestJobs: def test_method_create(self, client: Mixedbread) -> None: job = client.extractions.jobs.create( file_id="file_id", - json_schema={}, + json_schema={"foo": "bar"}, ) assert_matches_type(ExtractionJob, job, path=["response"]) @@ -29,7 +29,7 @@ def test_method_create(self, client: Mixedbread) -> None: def test_raw_response_create(self, client: Mixedbread) -> None: response = client.extractions.jobs.with_raw_response.create( file_id="file_id", - json_schema={}, + json_schema={"foo": "bar"}, ) assert response.is_closed is True @@ -41,7 +41,7 @@ def test_raw_response_create(self, client: Mixedbread) -> None: def test_streaming_response_create(self, client: Mixedbread) -> None: with client.extractions.jobs.with_streaming_response.create( file_id="file_id", - json_schema={}, + json_schema={"foo": "bar"}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -91,13 +91,15 @@ def test_path_params_retrieve(self, client: Mixedbread) -> None: class TestAsyncJobs: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncMixedbread) -> None: job = await async_client.extractions.jobs.create( file_id="file_id", - json_schema={}, + json_schema={"foo": "bar"}, ) assert_matches_type(ExtractionJob, job, path=["response"]) @@ -105,7 +107,7 @@ async def test_method_create(self, async_client: AsyncMixedbread) -> None: async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: response = await async_client.extractions.jobs.with_raw_response.create( file_id="file_id", - json_schema={}, + json_schema={"foo": "bar"}, ) assert response.is_closed is True @@ -117,7 +119,7 @@ async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: async with async_client.extractions.jobs.with_streaming_response.create( file_id="file_id", - json_schema={}, + json_schema={"foo": "bar"}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/extractions/test_schema.py b/tests/api_resources/extractions/test_schema.py index 5bff1298..3de149ff 100644 --- a/tests/api_resources/extractions/test_schema.py +++ b/tests/api_resources/extractions/test_schema.py @@ -55,14 +55,14 @@ def test_streaming_response_create(self, client: Mixedbread) -> None: @parametrize def test_method_enhance(self, client: Mixedbread) -> None: schema = client.extractions.schema.enhance( - json_schema={}, + json_schema={"foo": "bar"}, ) assert_matches_type(EnhancedJsonSchema, schema, path=["response"]) @parametrize def test_raw_response_enhance(self, client: Mixedbread) -> None: response = client.extractions.schema.with_raw_response.enhance( - json_schema={}, + json_schema={"foo": "bar"}, ) assert response.is_closed is True @@ -73,7 +73,7 @@ def test_raw_response_enhance(self, client: Mixedbread) -> None: @parametrize def test_streaming_response_enhance(self, client: Mixedbread) -> None: with client.extractions.schema.with_streaming_response.enhance( - json_schema={}, + json_schema={"foo": "bar"}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -86,14 +86,14 @@ def test_streaming_response_enhance(self, client: Mixedbread) -> None: @parametrize def test_method_validate(self, client: Mixedbread) -> None: schema = client.extractions.schema.validate( - json_schema={}, + json_schema={"foo": "bar"}, ) assert_matches_type(ValidatedJsonSchema, schema, path=["response"]) @parametrize def test_raw_response_validate(self, client: Mixedbread) -> None: response = client.extractions.schema.with_raw_response.validate( - json_schema={}, + json_schema={"foo": "bar"}, ) assert response.is_closed is True @@ -104,7 +104,7 @@ def test_raw_response_validate(self, client: Mixedbread) -> None: @parametrize def test_streaming_response_validate(self, client: Mixedbread) -> None: with client.extractions.schema.with_streaming_response.validate( - json_schema={}, + json_schema={"foo": "bar"}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -116,7 +116,9 @@ def test_streaming_response_validate(self, client: Mixedbread) -> None: class TestAsyncSchema: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncMixedbread) -> None: @@ -152,14 +154,14 @@ async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> @parametrize async def test_method_enhance(self, async_client: AsyncMixedbread) -> None: schema = await async_client.extractions.schema.enhance( - json_schema={}, + json_schema={"foo": "bar"}, ) assert_matches_type(EnhancedJsonSchema, schema, path=["response"]) @parametrize async def test_raw_response_enhance(self, async_client: AsyncMixedbread) -> None: response = await async_client.extractions.schema.with_raw_response.enhance( - json_schema={}, + json_schema={"foo": "bar"}, ) assert response.is_closed is True @@ -170,7 +172,7 @@ async def test_raw_response_enhance(self, async_client: AsyncMixedbread) -> None @parametrize async def test_streaming_response_enhance(self, async_client: AsyncMixedbread) -> None: async with async_client.extractions.schema.with_streaming_response.enhance( - json_schema={}, + json_schema={"foo": "bar"}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -183,14 +185,14 @@ async def test_streaming_response_enhance(self, async_client: AsyncMixedbread) - @parametrize async def test_method_validate(self, async_client: AsyncMixedbread) -> None: schema = await async_client.extractions.schema.validate( - json_schema={}, + json_schema={"foo": "bar"}, ) assert_matches_type(ValidatedJsonSchema, schema, path=["response"]) @parametrize async def test_raw_response_validate(self, async_client: AsyncMixedbread) -> None: response = await async_client.extractions.schema.with_raw_response.validate( - json_schema={}, + json_schema={"foo": "bar"}, ) assert response.is_closed is True @@ -201,7 +203,7 @@ async def test_raw_response_validate(self, async_client: AsyncMixedbread) -> Non @parametrize async def test_streaming_response_validate(self, async_client: AsyncMixedbread) -> None: async with async_client.extractions.schema.with_streaming_response.validate( - json_schema={}, + json_schema={"foo": "bar"}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/files/test_content.py b/tests/api_resources/files/test_content.py deleted file mode 100644 index d8d219f2..00000000 --- a/tests/api_resources/files/test_content.py +++ /dev/null @@ -1,140 +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 httpx -import pytest -from respx import MockRouter - -from mixedbread import Mixedbread, AsyncMixedbread -from mixedbread._response import ( - BinaryAPIResponse, - AsyncBinaryAPIResponse, - StreamedBinaryAPIResponse, - AsyncStreamedBinaryAPIResponse, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestContent: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_retrieve(self, client: Mixedbread, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - content = client.files.content.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert content.is_closed - assert content.json() == {"foo": "bar"} - assert cast(Any, content.is_closed) is True - assert isinstance(content, BinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_retrieve(self, client: Mixedbread, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - - content = client.files.content.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert content.is_closed is True - assert content.http_request.headers.get("X-Stainless-Lang") == "python" - assert content.json() == {"foo": "bar"} - assert isinstance(content, BinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_retrieve(self, client: Mixedbread, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - with client.files.content.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as content: - assert not content.is_closed - assert content.http_request.headers.get("X-Stainless-Lang") == "python" - - assert content.json() == {"foo": "bar"} - assert cast(Any, content.is_closed) is True - assert isinstance(content, StreamedBinaryAPIResponse) - - assert cast(Any, content.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_path_params_retrieve(self, client: Mixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - client.files.content.with_raw_response.retrieve( - "", - ) - - -class TestAsyncContent: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_retrieve(self, async_client: AsyncMixedbread, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - content = await async_client.files.content.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert content.is_closed - assert await content.json() == {"foo": "bar"} - assert cast(Any, content.is_closed) is True - assert isinstance(content, AsyncBinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_retrieve(self, async_client: AsyncMixedbread, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - - content = await async_client.files.content.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert content.is_closed is True - assert content.http_request.headers.get("X-Stainless-Lang") == "python" - assert await content.json() == {"foo": "bar"} - assert isinstance(content, AsyncBinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - async with async_client.files.content.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as content: - assert not content.is_closed - assert content.http_request.headers.get("X-Stainless-Lang") == "python" - - assert await content.json() == {"foo": "bar"} - assert cast(Any, content.is_closed) is True - assert isinstance(content, AsyncStreamedBinaryAPIResponse) - - assert cast(Any, content.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - await async_client.files.content.with_raw_response.retrieve( - "", - ) diff --git a/tests/api_resources/files/test_uploads.py b/tests/api_resources/files/test_uploads.py new file mode 100644 index 00000000..2dd222a5 --- /dev/null +++ b/tests/api_resources/files/test_uploads.py @@ -0,0 +1,450 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.types import FileObject +from mixedbread.types.files import ( + UploadListResponse, + UploadAbortResponse, + UploadCreateResponse, + UploadRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestUploads: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Mixedbread) -> None: + upload = client.files.uploads.create( + filename="document.pdf", + file_size=10485760, + mime_type="application/pdf", + ) + assert_matches_type(UploadCreateResponse, upload, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Mixedbread) -> None: + upload = client.files.uploads.create( + filename="document.pdf", + file_size=10485760, + mime_type="application/pdf", + part_count=3, + ) + assert_matches_type(UploadCreateResponse, upload, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Mixedbread) -> None: + response = client.files.uploads.with_raw_response.create( + filename="document.pdf", + file_size=10485760, + mime_type="application/pdf", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + upload = response.parse() + assert_matches_type(UploadCreateResponse, upload, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Mixedbread) -> None: + with client.files.uploads.with_streaming_response.create( + filename="document.pdf", + file_size=10485760, + mime_type="application/pdf", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + upload = response.parse() + assert_matches_type(UploadCreateResponse, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Mixedbread) -> None: + upload = client.files.uploads.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(UploadRetrieveResponse, upload, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Mixedbread) -> None: + response = client.files.uploads.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" + upload = response.parse() + assert_matches_type(UploadRetrieveResponse, upload, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Mixedbread) -> None: + with client.files.uploads.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" + + upload = response.parse() + assert_matches_type(UploadRetrieveResponse, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): + client.files.uploads.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + upload = client.files.uploads.list() + assert_matches_type(UploadListResponse, upload, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.files.uploads.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + upload = response.parse() + assert_matches_type(UploadListResponse, upload, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.files.uploads.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + upload = response.parse() + assert_matches_type(UploadListResponse, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_abort(self, client: Mixedbread) -> None: + upload = client.files.uploads.abort( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(UploadAbortResponse, upload, path=["response"]) + + @parametrize + def test_raw_response_abort(self, client: Mixedbread) -> None: + response = client.files.uploads.with_raw_response.abort( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + upload = response.parse() + assert_matches_type(UploadAbortResponse, upload, path=["response"]) + + @parametrize + def test_streaming_response_abort(self, client: Mixedbread) -> None: + with client.files.uploads.with_streaming_response.abort( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + upload = response.parse() + assert_matches_type(UploadAbortResponse, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_abort(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): + client.files.uploads.with_raw_response.abort( + "", + ) + + @parametrize + def test_method_complete(self, client: Mixedbread) -> None: + upload = client.files.uploads.complete( + upload_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + parts=[ + { + "part_number": 1, + "etag": "etag", + } + ], + ) + assert_matches_type(FileObject, upload, path=["response"]) + + @parametrize + def test_raw_response_complete(self, client: Mixedbread) -> None: + response = client.files.uploads.with_raw_response.complete( + upload_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + parts=[ + { + "part_number": 1, + "etag": "etag", + } + ], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + upload = response.parse() + assert_matches_type(FileObject, upload, path=["response"]) + + @parametrize + def test_streaming_response_complete(self, client: Mixedbread) -> None: + with client.files.uploads.with_streaming_response.complete( + upload_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + parts=[ + { + "part_number": 1, + "etag": "etag", + } + ], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + upload = response.parse() + assert_matches_type(FileObject, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_complete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): + client.files.uploads.with_raw_response.complete( + upload_id="", + parts=[ + { + "part_number": 1, + "etag": "etag", + } + ], + ) + + +class TestAsyncUploads: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncMixedbread) -> None: + upload = await async_client.files.uploads.create( + filename="document.pdf", + file_size=10485760, + mime_type="application/pdf", + ) + assert_matches_type(UploadCreateResponse, upload, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: + upload = await async_client.files.uploads.create( + filename="document.pdf", + file_size=10485760, + mime_type="application/pdf", + part_count=3, + ) + assert_matches_type(UploadCreateResponse, upload, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: + response = await async_client.files.uploads.with_raw_response.create( + filename="document.pdf", + file_size=10485760, + mime_type="application/pdf", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + upload = await response.parse() + assert_matches_type(UploadCreateResponse, upload, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: + async with async_client.files.uploads.with_streaming_response.create( + filename="document.pdf", + file_size=10485760, + mime_type="application/pdf", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + upload = await response.parse() + assert_matches_type(UploadCreateResponse, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: + upload = await async_client.files.uploads.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(UploadRetrieveResponse, upload, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: + response = await async_client.files.uploads.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" + upload = await response.parse() + assert_matches_type(UploadRetrieveResponse, upload, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: + async with async_client.files.uploads.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" + + upload = await response.parse() + assert_matches_type(UploadRetrieveResponse, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): + await async_client.files.uploads.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + upload = await async_client.files.uploads.list() + assert_matches_type(UploadListResponse, upload, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.files.uploads.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + upload = await response.parse() + assert_matches_type(UploadListResponse, upload, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.files.uploads.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + upload = await response.parse() + assert_matches_type(UploadListResponse, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_abort(self, async_client: AsyncMixedbread) -> None: + upload = await async_client.files.uploads.abort( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(UploadAbortResponse, upload, path=["response"]) + + @parametrize + async def test_raw_response_abort(self, async_client: AsyncMixedbread) -> None: + response = await async_client.files.uploads.with_raw_response.abort( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + upload = await response.parse() + assert_matches_type(UploadAbortResponse, upload, path=["response"]) + + @parametrize + async def test_streaming_response_abort(self, async_client: AsyncMixedbread) -> None: + async with async_client.files.uploads.with_streaming_response.abort( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + upload = await response.parse() + assert_matches_type(UploadAbortResponse, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_abort(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): + await async_client.files.uploads.with_raw_response.abort( + "", + ) + + @parametrize + async def test_method_complete(self, async_client: AsyncMixedbread) -> None: + upload = await async_client.files.uploads.complete( + upload_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + parts=[ + { + "part_number": 1, + "etag": "etag", + } + ], + ) + assert_matches_type(FileObject, upload, path=["response"]) + + @parametrize + async def test_raw_response_complete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.files.uploads.with_raw_response.complete( + upload_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + parts=[ + { + "part_number": 1, + "etag": "etag", + } + ], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + upload = await response.parse() + assert_matches_type(FileObject, upload, path=["response"]) + + @parametrize + async def test_streaming_response_complete(self, async_client: AsyncMixedbread) -> None: + async with async_client.files.uploads.with_streaming_response.complete( + upload_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + parts=[ + { + "part_number": 1, + "etag": "etag", + } + ], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + upload = await response.parse() + assert_matches_type(FileObject, upload, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_complete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `upload_id` but received ''"): + await async_client.files.uploads.with_raw_response.complete( + upload_id="", + parts=[ + { + "part_number": 1, + "etag": "etag", + } + ], + ) diff --git a/tests/api_resources/parsing/test_jobs.py b/tests/api_resources/parsing/test_jobs.py index 1ec0df5b..50842fb3 100644 --- a/tests/api_resources/parsing/test_jobs.py +++ b/tests/api_resources/parsing/test_jobs.py @@ -9,7 +9,12 @@ from mixedbread import Mixedbread, AsyncMixedbread from tests.utils import assert_matches_type -from mixedbread.types.parsing import ParsingJob +from mixedbread.pagination import SyncCursor, AsyncCursor +from mixedbread.types.parsing import ( + ParsingJob, + JobListResponse, + JobDeleteResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -28,9 +33,10 @@ def test_method_create(self, client: Mixedbread) -> None: def test_method_create_with_all_params(self, client: Mixedbread) -> None: job = client.parsing.jobs.create( file_id="file_id", + element_types=["header"], chunking_strategy="page", - element_types=["caption"], return_format="html", + mode="fast", ) assert_matches_type(ParsingJob, job, path=["response"]) @@ -61,14 +67,14 @@ def test_streaming_response_create(self, client: Mixedbread) -> None: @parametrize def test_method_retrieve(self, client: Mixedbread) -> None: job = client.parsing.jobs.retrieve( - "job_id", + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ParsingJob, job, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Mixedbread) -> None: response = client.parsing.jobs.with_raw_response.retrieve( - "job_id", + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -79,7 +85,7 @@ def test_raw_response_retrieve(self, client: Mixedbread) -> None: @parametrize def test_streaming_response_retrieve(self, client: Mixedbread) -> None: with client.parsing.jobs.with_streaming_response.retrieve( - "job_id", + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -96,9 +102,124 @@ def test_path_params_retrieve(self, client: Mixedbread) -> None: "", ) + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + job = client.parsing.jobs.list() + assert_matches_type(SyncCursor[JobListResponse], job, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Mixedbread) -> None: + job = client.parsing.jobs.list( + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + statuses=["pending", "in_progress"], + q="x", + ) + assert_matches_type(SyncCursor[JobListResponse], job, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.parsing.jobs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = response.parse() + assert_matches_type(SyncCursor[JobListResponse], job, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.parsing.jobs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(SyncCursor[JobListResponse], job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Mixedbread) -> None: + job = client.parsing.jobs.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(JobDeleteResponse, job, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Mixedbread) -> None: + response = client.parsing.jobs.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = response.parse() + assert_matches_type(JobDeleteResponse, job, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Mixedbread) -> None: + with client.parsing.jobs.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(JobDeleteResponse, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"): + client.parsing.jobs.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_cancel(self, client: Mixedbread) -> None: + job = client.parsing.jobs.cancel( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ParsingJob, job, path=["response"]) + + @parametrize + def test_raw_response_cancel(self, client: Mixedbread) -> None: + response = client.parsing.jobs.with_raw_response.cancel( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = response.parse() + assert_matches_type(ParsingJob, job, path=["response"]) + + @parametrize + def test_streaming_response_cancel(self, client: Mixedbread) -> None: + with client.parsing.jobs.with_streaming_response.cancel( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(ParsingJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_cancel(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"): + client.parsing.jobs.with_raw_response.cancel( + "", + ) + class TestAsyncJobs: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncMixedbread) -> None: @@ -111,9 +232,10 @@ async def test_method_create(self, async_client: AsyncMixedbread) -> None: async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: job = await async_client.parsing.jobs.create( file_id="file_id", + element_types=["header"], chunking_strategy="page", - element_types=["caption"], return_format="html", + mode="fast", ) assert_matches_type(ParsingJob, job, path=["response"]) @@ -144,14 +266,14 @@ async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> @parametrize async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: job = await async_client.parsing.jobs.retrieve( - "job_id", + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ParsingJob, job, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: response = await async_client.parsing.jobs.with_raw_response.retrieve( - "job_id", + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -162,7 +284,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> Non @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: async with async_client.parsing.jobs.with_streaming_response.retrieve( - "job_id", + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -178,3 +300,116 @@ async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None await async_client.parsing.jobs.with_raw_response.retrieve( "", ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + job = await async_client.parsing.jobs.list() + assert_matches_type(AsyncCursor[JobListResponse], job, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: + job = await async_client.parsing.jobs.list( + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + statuses=["pending", "in_progress"], + q="x", + ) + assert_matches_type(AsyncCursor[JobListResponse], job, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.parsing.jobs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = await response.parse() + assert_matches_type(AsyncCursor[JobListResponse], job, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.parsing.jobs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(AsyncCursor[JobListResponse], job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncMixedbread) -> None: + job = await async_client.parsing.jobs.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(JobDeleteResponse, job, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.parsing.jobs.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = await response.parse() + assert_matches_type(JobDeleteResponse, job, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: + async with async_client.parsing.jobs.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(JobDeleteResponse, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"): + await async_client.parsing.jobs.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_cancel(self, async_client: AsyncMixedbread) -> None: + job = await async_client.parsing.jobs.cancel( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ParsingJob, job, path=["response"]) + + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncMixedbread) -> None: + response = await async_client.parsing.jobs.with_raw_response.cancel( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + job = await response.parse() + assert_matches_type(ParsingJob, job, path=["response"]) + + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncMixedbread) -> None: + async with async_client.parsing.jobs.with_streaming_response.cancel( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(ParsingJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_cancel(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"): + await async_client.parsing.jobs.with_raw_response.cancel( + "", + ) diff --git a/tests/api_resources/stores/__init__.py b/tests/api_resources/stores/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/stores/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/stores/test_files.py b/tests/api_resources/stores/test_files.py new file mode 100644 index 00000000..f59802bf --- /dev/null +++ b/tests/api_resources/stores/test_files.py @@ -0,0 +1,693 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.types.stores import ( + StoreFile, + FileListResponse, + FileDeleteResponse, + FileSearchResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestFiles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Mixedbread) -> None: + file = client.stores.files.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.create( + store_identifier="store_identifier", + metadata={}, + config={"parsing_strategy": "fast"}, + external_id="external_id", + overwrite=False, + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + experimental={"parsing_strategy": "fast"}, + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.create( + store_identifier="", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + def test_method_retrieve(self, client: Mixedbread) -> None: + file = client.stores.files.retrieve( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.retrieve( + file_identifier="file_identifier", + store_identifier="store_identifier", + return_chunks=True, + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.retrieve( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.retrieve( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.retrieve( + file_identifier="file_identifier", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_identifier` but received ''"): + client.stores.files.with_raw_response.retrieve( + file_identifier="", + store_identifier="store_identifier", + ) + + @parametrize + def test_method_update(self, client: Mixedbread) -> None: + file = client.stores.files.update( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.update( + file_identifier="file_identifier", + store_identifier="store_identifier", + metadata={"foo": "bar"}, + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.update( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.update( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.update( + file_identifier="file_identifier", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_identifier` but received ''"): + client.stores.files.with_raw_response.update( + file_identifier="", + store_identifier="store_identifier", + ) + + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + file = client.stores.files.list( + store_identifier="store_identifier", + ) + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.list( + store_identifier="store_identifier", + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + statuses=["pending"], + metadata_filter={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + q="x", + ) + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.list( + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.list( + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileListResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.list( + store_identifier="", + ) + + @parametrize + def test_method_delete(self, client: Mixedbread) -> None: + file = client.stores.files.delete( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.delete( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.delete( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.delete( + file_identifier="file_identifier", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_identifier` but received ''"): + client.stores.files.with_raw_response.delete( + file_identifier="", + store_identifier="store_identifier", + ) + + @parametrize + def test_method_search(self, client: Mixedbread) -> None: + file = client.stores.files.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + def test_method_search_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.search( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "agentic": True, + "return_metadata": True, + "return_chunks": True, + "chunks_per_file": 0, + "apply_search_rules": True, + }, + ) + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + def test_raw_response_search(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + def test_streaming_response_search(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileSearchResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncFiles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.create( + store_identifier="store_identifier", + metadata={}, + config={"parsing_strategy": "fast"}, + external_id="external_id", + overwrite=False, + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + experimental={"parsing_strategy": "fast"}, + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.create( + store_identifier="", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.retrieve( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.retrieve( + file_identifier="file_identifier", + store_identifier="store_identifier", + return_chunks=True, + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.retrieve( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.retrieve( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.retrieve( + file_identifier="file_identifier", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_identifier` but received ''"): + await async_client.stores.files.with_raw_response.retrieve( + file_identifier="", + store_identifier="store_identifier", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.update( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.update( + file_identifier="file_identifier", + store_identifier="store_identifier", + metadata={"foo": "bar"}, + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.update( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.update( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.update( + file_identifier="file_identifier", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_identifier` but received ''"): + await async_client.stores.files.with_raw_response.update( + file_identifier="", + store_identifier="store_identifier", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.list( + store_identifier="store_identifier", + ) + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.list( + store_identifier="store_identifier", + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + statuses=["pending"], + metadata_filter={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + q="x", + ) + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.list( + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.list( + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileListResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.list( + store_identifier="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.delete( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.delete( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.delete( + file_identifier="file_identifier", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.delete( + file_identifier="file_identifier", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_identifier` but received ''"): + await async_client.stores.files.with_raw_response.delete( + file_identifier="", + store_identifier="store_identifier", + ) + + @parametrize + async def test_method_search(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + async def test_method_search_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.search( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "agentic": True, + "return_metadata": True, + "return_chunks": True, + "chunks_per_file": 0, + "apply_search_rules": True, + }, + ) + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + async def test_raw_response_search(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + async def test_streaming_response_search(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileSearchResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_api_keys.py b/tests/api_resources/test_api_keys.py new file mode 100644 index 00000000..aa8310ee --- /dev/null +++ b/tests/api_resources/test_api_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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.types import APIKey, APIKeyCreated, APIKeyDeleteResponse +from mixedbread._utils import parse_datetime +from mixedbread.pagination import SyncLimitOffset, AsyncLimitOffset + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAPIKeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Mixedbread) -> None: + api_key = client.api_keys.create() + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Mixedbread) -> None: + api_key = client.api_keys.create( + name="name", + scope=[ + { + "method": "read", + "resource_type": "store", + "resource_id": "resource_id", + } + ], + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + ) + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Mixedbread) -> None: + response = client.api_keys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Mixedbread) -> None: + with client.api_keys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Mixedbread) -> None: + api_key = client.api_keys.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(APIKey, api_key, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Mixedbread) -> None: + response = client.api_keys.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" + api_key = response.parse() + assert_matches_type(APIKey, api_key, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Mixedbread) -> None: + with client.api_keys.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" + + api_key = response.parse() + assert_matches_type(APIKey, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + client.api_keys.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + api_key = client.api_keys.list() + assert_matches_type(SyncLimitOffset[APIKey], api_key, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Mixedbread) -> None: + api_key = client.api_keys.list( + limit=1000, + offset=0, + ) + assert_matches_type(SyncLimitOffset[APIKey], api_key, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.api_keys.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(SyncLimitOffset[APIKey], api_key, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.api_keys.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(SyncLimitOffset[APIKey], api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Mixedbread) -> None: + api_key = client.api_keys.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Mixedbread) -> None: + response = client.api_keys.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Mixedbread) -> None: + with client.api_keys.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + client.api_keys.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_reroll(self, client: Mixedbread) -> None: + api_key = client.api_keys.reroll( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + def test_raw_response_reroll(self, client: Mixedbread) -> None: + response = client.api_keys.with_raw_response.reroll( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + def test_streaming_response_reroll(self, client: Mixedbread) -> None: + with client.api_keys.with_streaming_response.reroll( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_reroll(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + client.api_keys.with_raw_response.reroll( + "", + ) + + @parametrize + def test_method_revoke(self, client: Mixedbread) -> None: + api_key = client.api_keys.revoke( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(APIKey, api_key, path=["response"]) + + @parametrize + def test_raw_response_revoke(self, client: Mixedbread) -> None: + response = client.api_keys.with_raw_response.revoke( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(APIKey, api_key, path=["response"]) + + @parametrize + def test_streaming_response_revoke(self, client: Mixedbread) -> None: + with client.api_keys.with_streaming_response.revoke( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(APIKey, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_revoke(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + client.api_keys.with_raw_response.revoke( + "", + ) + + +class TestAsyncAPIKeys: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncMixedbread) -> None: + api_key = await async_client.api_keys.create() + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: + api_key = await async_client.api_keys.create( + name="name", + scope=[ + { + "method": "read", + "resource_type": "store", + "resource_id": "resource_id", + } + ], + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + ) + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: + response = await async_client.api_keys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = await response.parse() + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: + async with async_client.api_keys.with_streaming_response.create() 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(APIKeyCreated, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: + api_key = await async_client.api_keys.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(APIKey, api_key, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: + response = await async_client.api_keys.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" + api_key = await response.parse() + assert_matches_type(APIKey, api_key, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: + async with async_client.api_keys.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" + + api_key = await response.parse() + assert_matches_type(APIKey, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + await async_client.api_keys.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + api_key = await async_client.api_keys.list() + assert_matches_type(AsyncLimitOffset[APIKey], api_key, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: + api_key = await async_client.api_keys.list( + limit=1000, + offset=0, + ) + assert_matches_type(AsyncLimitOffset[APIKey], api_key, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.api_keys.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = await response.parse() + assert_matches_type(AsyncLimitOffset[APIKey], api_key, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.api_keys.with_streaming_response.list() 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(AsyncLimitOffset[APIKey], api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncMixedbread) -> None: + api_key = await async_client.api_keys.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.api_keys.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = await response.parse() + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: + async with async_client.api_keys.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = await response.parse() + assert_matches_type(APIKeyDeleteResponse, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + await async_client.api_keys.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_reroll(self, async_client: AsyncMixedbread) -> None: + api_key = await async_client.api_keys.reroll( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + async def test_raw_response_reroll(self, async_client: AsyncMixedbread) -> None: + response = await async_client.api_keys.with_raw_response.reroll( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = await response.parse() + assert_matches_type(APIKeyCreated, api_key, path=["response"]) + + @parametrize + async def test_streaming_response_reroll(self, async_client: AsyncMixedbread) -> None: + async with async_client.api_keys.with_streaming_response.reroll( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) 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(APIKeyCreated, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_reroll(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + await async_client.api_keys.with_raw_response.reroll( + "", + ) + + @parametrize + async def test_method_revoke(self, async_client: AsyncMixedbread) -> None: + api_key = await async_client.api_keys.revoke( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(APIKey, api_key, path=["response"]) + + @parametrize + async def test_raw_response_revoke(self, async_client: AsyncMixedbread) -> None: + response = await async_client.api_keys.with_raw_response.revoke( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = await response.parse() + assert_matches_type(APIKey, api_key, path=["response"]) + + @parametrize + async def test_streaming_response_revoke(self, async_client: AsyncMixedbread) -> None: + async with async_client.api_keys.with_streaming_response.revoke( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) 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(APIKey, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_revoke(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `api_key_id` but received ''"): + await async_client.api_keys.with_raw_response.revoke( + "", + ) diff --git a/tests/api_resources/test_chat.py b/tests/api_resources/test_chat.py new file mode 100644 index 00000000..a60009fc --- /dev/null +++ b/tests/api_resources/test_chat.py @@ -0,0 +1,73 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestChat: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create_completion(self, client: Mixedbread) -> None: + chat = client.chat.create_completion() + assert_matches_type(object, chat, path=["response"]) + + @parametrize + def test_raw_response_create_completion(self, client: Mixedbread) -> None: + response = client.chat.with_raw_response.create_completion() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + chat = response.parse() + assert_matches_type(object, chat, path=["response"]) + + @parametrize + def test_streaming_response_create_completion(self, client: Mixedbread) -> None: + with client.chat.with_streaming_response.create_completion() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + chat = response.parse() + assert_matches_type(object, chat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncChat: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create_completion(self, async_client: AsyncMixedbread) -> None: + chat = await async_client.chat.create_completion() + assert_matches_type(object, chat, path=["response"]) + + @parametrize + async def test_raw_response_create_completion(self, async_client: AsyncMixedbread) -> None: + response = await async_client.chat.with_raw_response.create_completion() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + chat = await response.parse() + assert_matches_type(object, chat, path=["response"]) + + @parametrize + async def test_streaming_response_create_completion(self, async_client: AsyncMixedbread) -> None: + async with async_client.chat.with_streaming_response.create_completion() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + chat = await response.parse() + assert_matches_type(object, chat, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_client.py b/tests/api_resources/test_client.py new file mode 100644 index 00000000..7a05f7de --- /dev/null +++ b/tests/api_resources/test_client.py @@ -0,0 +1,264 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.types import ( + InfoResponse, + RerankResponse, + EmbeddingCreateResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestClient: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_embed(self, client: Mixedbread) -> None: + client_ = client.embed( + model="mixedbread-ai/mxbai-embed-large-v1", + input="x", + ) + assert_matches_type(EmbeddingCreateResponse, client_, path=["response"]) + + @parametrize + def test_method_embed_with_all_params(self, client: Mixedbread) -> None: + client_ = client.embed( + model="mixedbread-ai/mxbai-embed-large-v1", + input="x", + dimensions=768, + prompt="Provide a detailed summary of the following text.", + normalized=True, + encoding_format="float", + ) + assert_matches_type(EmbeddingCreateResponse, client_, path=["response"]) + + @parametrize + def test_raw_response_embed(self, client: Mixedbread) -> None: + response = client.with_raw_response.embed( + model="mixedbread-ai/mxbai-embed-large-v1", + input="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + client_ = response.parse() + assert_matches_type(EmbeddingCreateResponse, client_, path=["response"]) + + @parametrize + def test_streaming_response_embed(self, client: Mixedbread) -> None: + with client.with_streaming_response.embed( + model="mixedbread-ai/mxbai-embed-large-v1", + input="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + client_ = response.parse() + assert_matches_type(EmbeddingCreateResponse, client_, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_info(self, client: Mixedbread) -> None: + client_ = client.info() + assert_matches_type(InfoResponse, client_, path=["response"]) + + @parametrize + def test_raw_response_info(self, client: Mixedbread) -> None: + response = client.with_raw_response.info() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + client_ = response.parse() + assert_matches_type(InfoResponse, client_, path=["response"]) + + @parametrize + def test_streaming_response_info(self, client: Mixedbread) -> None: + with client.with_streaming_response.info() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + client_ = response.parse() + assert_matches_type(InfoResponse, client_, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_rerank(self, client: Mixedbread) -> None: + client_ = client.rerank( + query="What are the key features of the Mixedbread embedding model?", + input=["Document 1", "Document 2"], + ) + assert_matches_type(RerankResponse, client_, path=["response"]) + + @parametrize + def test_method_rerank_with_all_params(self, client: Mixedbread) -> None: + client_ = client.rerank( + model="mixedbread-ai/mxbai-rerank-large-v2", + query="What are the key features of the Mixedbread embedding model?", + input=["Document 1", "Document 2"], + rank_fields=["content", "title"], + top_k=10, + return_input=False, + rewrite_query=False, + ) + assert_matches_type(RerankResponse, client_, path=["response"]) + + @parametrize + def test_raw_response_rerank(self, client: Mixedbread) -> None: + response = client.with_raw_response.rerank( + query="What are the key features of the Mixedbread embedding model?", + input=["Document 1", "Document 2"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + client_ = response.parse() + assert_matches_type(RerankResponse, client_, path=["response"]) + + @parametrize + def test_streaming_response_rerank(self, client: Mixedbread) -> None: + with client.with_streaming_response.rerank( + query="What are the key features of the Mixedbread embedding model?", + input=["Document 1", "Document 2"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + client_ = response.parse() + assert_matches_type(RerankResponse, client_, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncClient: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_embed(self, async_client: AsyncMixedbread) -> None: + client = await async_client.embed( + model="mixedbread-ai/mxbai-embed-large-v1", + input="x", + ) + assert_matches_type(EmbeddingCreateResponse, client, path=["response"]) + + @parametrize + async def test_method_embed_with_all_params(self, async_client: AsyncMixedbread) -> None: + client = await async_client.embed( + model="mixedbread-ai/mxbai-embed-large-v1", + input="x", + dimensions=768, + prompt="Provide a detailed summary of the following text.", + normalized=True, + encoding_format="float", + ) + assert_matches_type(EmbeddingCreateResponse, client, path=["response"]) + + @parametrize + async def test_raw_response_embed(self, async_client: AsyncMixedbread) -> None: + response = await async_client.with_raw_response.embed( + model="mixedbread-ai/mxbai-embed-large-v1", + input="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + client = await response.parse() + assert_matches_type(EmbeddingCreateResponse, client, path=["response"]) + + @parametrize + async def test_streaming_response_embed(self, async_client: AsyncMixedbread) -> None: + async with async_client.with_streaming_response.embed( + model="mixedbread-ai/mxbai-embed-large-v1", + input="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + client = await response.parse() + assert_matches_type(EmbeddingCreateResponse, client, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_info(self, async_client: AsyncMixedbread) -> None: + client = await async_client.info() + assert_matches_type(InfoResponse, client, path=["response"]) + + @parametrize + async def test_raw_response_info(self, async_client: AsyncMixedbread) -> None: + response = await async_client.with_raw_response.info() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + client = await response.parse() + assert_matches_type(InfoResponse, client, path=["response"]) + + @parametrize + async def test_streaming_response_info(self, async_client: AsyncMixedbread) -> None: + async with async_client.with_streaming_response.info() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + client = await response.parse() + assert_matches_type(InfoResponse, client, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_rerank(self, async_client: AsyncMixedbread) -> None: + client = await async_client.rerank( + query="What are the key features of the Mixedbread embedding model?", + input=["Document 1", "Document 2"], + ) + assert_matches_type(RerankResponse, client, path=["response"]) + + @parametrize + async def test_method_rerank_with_all_params(self, async_client: AsyncMixedbread) -> None: + client = await async_client.rerank( + model="mixedbread-ai/mxbai-rerank-large-v2", + query="What are the key features of the Mixedbread embedding model?", + input=["Document 1", "Document 2"], + rank_fields=["content", "title"], + top_k=10, + return_input=False, + rewrite_query=False, + ) + assert_matches_type(RerankResponse, client, path=["response"]) + + @parametrize + async def test_raw_response_rerank(self, async_client: AsyncMixedbread) -> None: + response = await async_client.with_raw_response.rerank( + query="What are the key features of the Mixedbread embedding model?", + input=["Document 1", "Document 2"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + client = await response.parse() + assert_matches_type(RerankResponse, client, path=["response"]) + + @parametrize + async def test_streaming_response_rerank(self, async_client: AsyncMixedbread) -> None: + async with async_client.with_streaming_response.rerank( + query="What are the key features of the Mixedbread embedding model?", + input=["Document 1", "Document 2"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + client = await response.parse() + assert_matches_type(RerankResponse, client, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_completions.py b/tests/api_resources/test_completions.py deleted file mode 100644 index af7af678..00000000 --- a/tests/api_resources/test_completions.py +++ /dev/null @@ -1,71 +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 mixedbread import Mixedbread, AsyncMixedbread -from tests.utils import assert_matches_type - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestCompletions: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_create(self, client: Mixedbread) -> None: - completion = client.completions.create() - assert_matches_type(object, completion, path=["response"]) - - @parametrize - def test_raw_response_create(self, client: Mixedbread) -> None: - response = client.completions.with_raw_response.create() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - completion = response.parse() - assert_matches_type(object, completion, path=["response"]) - - @parametrize - def test_streaming_response_create(self, client: Mixedbread) -> None: - with client.completions.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - completion = response.parse() - assert_matches_type(object, completion, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncCompletions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_create(self, async_client: AsyncMixedbread) -> None: - completion = await async_client.completions.create() - assert_matches_type(object, completion, path=["response"]) - - @parametrize - async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: - response = await async_client.completions.with_raw_response.create() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - completion = await response.parse() - assert_matches_type(object, completion, path=["response"]) - - @parametrize - async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: - async with async_client.completions.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - completion = await response.parse() - assert_matches_type(object, completion, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_data_sources.py b/tests/api_resources/test_data_sources.py new file mode 100644 index 00000000..5cb9991e --- /dev/null +++ b/tests/api_resources/test_data_sources.py @@ -0,0 +1,626 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.types import ( + DataSource, + DataSourceDeleteResponse, +) +from mixedbread.pagination import SyncCursor, AsyncCursor + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestDataSources: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create_overload_1(self, client: Mixedbread) -> None: + data_source = client.data_sources.create( + name="name", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_1(self, client: Mixedbread) -> None: + data_source = client.data_sources.create( + type="notion", + name="name", + metadata={}, + auth_params={"type": "oauth2"}, + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_raw_response_create_overload_1(self, client: Mixedbread) -> None: + response = client.data_sources.with_raw_response.create( + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_1(self, client: Mixedbread) -> None: + with client.data_sources.with_streaming_response.create( + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_create_overload_2(self, client: Mixedbread) -> None: + data_source = client.data_sources.create( + name="name", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_2(self, client: Mixedbread) -> None: + data_source = client.data_sources.create( + type="linear", + name="name", + metadata={}, + auth_params={"type": "oauth2"}, + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_raw_response_create_overload_2(self, client: Mixedbread) -> None: + response = client.data_sources.with_raw_response.create( + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_2(self, client: Mixedbread) -> None: + with client.data_sources.with_streaming_response.create( + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Mixedbread) -> None: + data_source = client.data_sources.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Mixedbread) -> None: + response = client.data_sources.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" + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Mixedbread) -> None: + with client.data_sources.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" + + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update_overload_1(self, client: Mixedbread) -> None: + data_source = client.data_sources.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_method_update_with_all_params_overload_1(self, client: Mixedbread) -> None: + data_source = client.data_sources.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="notion", + name="name", + metadata={}, + auth_params={"type": "oauth2"}, + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_raw_response_update_overload_1(self, client: Mixedbread) -> None: + response = client.data_sources.with_raw_response.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_streaming_response_update_overload_1(self, client: Mixedbread) -> None: + with client.data_sources.with_streaming_response.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update_overload_1(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.with_raw_response.update( + data_source_id="", + name="name", + ) + + @parametrize + def test_method_update_overload_2(self, client: Mixedbread) -> None: + data_source = client.data_sources.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_method_update_with_all_params_overload_2(self, client: Mixedbread) -> None: + data_source = client.data_sources.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="linear", + name="name", + metadata={}, + auth_params={"type": "oauth2"}, + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_raw_response_update_overload_2(self, client: Mixedbread) -> None: + response = client.data_sources.with_raw_response.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + def test_streaming_response_update_overload_2(self, client: Mixedbread) -> None: + with client.data_sources.with_streaming_response.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update_overload_2(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.with_raw_response.update( + data_source_id="", + name="name", + ) + + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + data_source = client.data_sources.list() + assert_matches_type(SyncCursor[DataSource], data_source, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Mixedbread) -> None: + data_source = client.data_sources.list( + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + ) + assert_matches_type(SyncCursor[DataSource], data_source, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.data_sources.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = response.parse() + assert_matches_type(SyncCursor[DataSource], data_source, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.data_sources.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = response.parse() + assert_matches_type(SyncCursor[DataSource], data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Mixedbread) -> None: + data_source = client.data_sources.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DataSourceDeleteResponse, data_source, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Mixedbread) -> None: + response = client.data_sources.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = response.parse() + assert_matches_type(DataSourceDeleteResponse, data_source, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Mixedbread) -> None: + with client.data_sources.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = response.parse() + assert_matches_type(DataSourceDeleteResponse, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + client.data_sources.with_raw_response.delete( + "", + ) + + +class TestAsyncDataSources: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create_overload_1(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.create( + name="name", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_1(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.create( + type="notion", + name="name", + metadata={}, + auth_params={"type": "oauth2"}, + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_1(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.with_raw_response.create( + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_1(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.with_streaming_response.create( + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_create_overload_2(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.create( + name="name", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_2(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.create( + type="linear", + name="name", + metadata={}, + auth_params={"type": "oauth2"}, + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_2(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.with_raw_response.create( + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_2(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.with_streaming_response.create( + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.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" + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.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" + + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update_overload_1(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_method_update_with_all_params_overload_1(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="notion", + name="name", + metadata={}, + auth_params={"type": "oauth2"}, + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_raw_response_update_overload_1(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.with_raw_response.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_streaming_response_update_overload_1(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.with_streaming_response.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_overload_1(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.with_raw_response.update( + data_source_id="", + name="name", + ) + + @parametrize + async def test_method_update_overload_2(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_method_update_with_all_params_overload_2(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="linear", + name="name", + metadata={}, + auth_params={"type": "oauth2"}, + ) + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_raw_response_update_overload_2(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.with_raw_response.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + @parametrize + async def test_streaming_response_update_overload_2(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.with_streaming_response.update( + data_source_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = await response.parse() + assert_matches_type(DataSource, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_overload_2(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.with_raw_response.update( + data_source_id="", + name="name", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.list() + assert_matches_type(AsyncCursor[DataSource], data_source, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.list( + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + ) + assert_matches_type(AsyncCursor[DataSource], data_source, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = await response.parse() + assert_matches_type(AsyncCursor[DataSource], data_source, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = await response.parse() + assert_matches_type(AsyncCursor[DataSource], data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncMixedbread) -> None: + data_source = await async_client.data_sources.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DataSourceDeleteResponse, data_source, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.data_sources.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + data_source = await response.parse() + assert_matches_type(DataSourceDeleteResponse, data_source, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: + async with async_client.data_sources.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + data_source = await response.parse() + assert_matches_type(DataSourceDeleteResponse, data_source, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `data_source_id` but received ''"): + await async_client.data_sources.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py index 62648a38..f962e880 100644 --- a/tests/api_resources/test_embeddings.py +++ b/tests/api_resources/test_embeddings.py @@ -20,28 +20,28 @@ class TestEmbeddings: @parametrize def test_method_create(self, client: Mixedbread) -> None: embedding = client.embeddings.create( - input="This is a sample text input.", model="mixedbread-ai/mxbai-embed-large-v1", + input="x", ) assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Mixedbread) -> None: embedding = client.embeddings.create( - input="This is a sample text input.", model="mixedbread-ai/mxbai-embed-large-v1", + input="x", dimensions=768, - encoding_format="float", - normalized=True, prompt="Provide a detailed summary of the following text.", + normalized=True, + encoding_format="float", ) assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @parametrize def test_raw_response_create(self, client: Mixedbread) -> None: response = client.embeddings.with_raw_response.create( - input="This is a sample text input.", model="mixedbread-ai/mxbai-embed-large-v1", + input="x", ) assert response.is_closed is True @@ -52,8 +52,8 @@ def test_raw_response_create(self, client: Mixedbread) -> None: @parametrize def test_streaming_response_create(self, client: Mixedbread) -> None: with client.embeddings.with_streaming_response.create( - input="This is a sample text input.", model="mixedbread-ai/mxbai-embed-large-v1", + input="x", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -65,33 +65,35 @@ def test_streaming_response_create(self, client: Mixedbread) -> None: class TestAsyncEmbeddings: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncMixedbread) -> None: embedding = await async_client.embeddings.create( - input="This is a sample text input.", model="mixedbread-ai/mxbai-embed-large-v1", + input="x", ) assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: embedding = await async_client.embeddings.create( - input="This is a sample text input.", model="mixedbread-ai/mxbai-embed-large-v1", + input="x", dimensions=768, - encoding_format="float", - normalized=True, prompt="Provide a detailed summary of the following text.", + normalized=True, + encoding_format="float", ) assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: response = await async_client.embeddings.with_raw_response.create( - input="This is a sample text input.", model="mixedbread-ai/mxbai-embed-large-v1", + input="x", ) assert response.is_closed is True @@ -102,8 +104,8 @@ async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: async with async_client.embeddings.with_streaming_response.create( - input="This is a sample text input.", model="mixedbread-ai/mxbai-embed-large-v1", + input="x", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py index 1685fe4e..62adce9b 100644 --- a/tests/api_resources/test_files.py +++ b/tests/api_resources/test_files.py @@ -5,15 +5,20 @@ import os from typing import Any, cast +import httpx import pytest +from respx import MockRouter from mixedbread import Mixedbread, AsyncMixedbread from tests.utils import assert_matches_type -from mixedbread.types import ( - FileObject, - FileDeleted, - FileListResponse, +from mixedbread.types import FileObject, FileDeleteResponse +from mixedbread._response import ( + BinaryAPIResponse, + AsyncBinaryAPIResponse, + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, ) +from mixedbread.pagination import SyncCursor, AsyncCursor base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -135,15 +140,18 @@ def test_path_params_update(self, client: Mixedbread) -> None: @parametrize def test_method_list(self, client: Mixedbread) -> None: file = client.files.list() - assert_matches_type(FileListResponse, file, path=["response"]) + assert_matches_type(SyncCursor[FileObject], file, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Mixedbread) -> None: file = client.files.list( - limit=0, - offset=0, + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + q="x", ) - assert_matches_type(FileListResponse, file, path=["response"]) + assert_matches_type(SyncCursor[FileObject], file, path=["response"]) @parametrize def test_raw_response_list(self, client: Mixedbread) -> None: @@ -152,7 +160,7 @@ def test_raw_response_list(self, client: Mixedbread) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(FileListResponse, file, path=["response"]) + assert_matches_type(SyncCursor[FileObject], file, path=["response"]) @parametrize def test_streaming_response_list(self, client: Mixedbread) -> None: @@ -161,7 +169,7 @@ def test_streaming_response_list(self, client: Mixedbread) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(FileListResponse, file, path=["response"]) + assert_matches_type(SyncCursor[FileObject], file, path=["response"]) assert cast(Any, response.is_closed) is True @@ -170,7 +178,7 @@ def test_method_delete(self, client: Mixedbread) -> None: file = client.files.delete( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(FileDeleted, file, path=["response"]) + assert_matches_type(FileDeleteResponse, file, path=["response"]) @parametrize def test_raw_response_delete(self, client: Mixedbread) -> None: @@ -181,7 +189,7 @@ def test_raw_response_delete(self, client: Mixedbread) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(FileDeleted, file, path=["response"]) + assert_matches_type(FileDeleteResponse, file, path=["response"]) @parametrize def test_streaming_response_delete(self, client: Mixedbread) -> None: @@ -192,7 +200,7 @@ def test_streaming_response_delete(self, client: Mixedbread) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert_matches_type(FileDeleted, file, path=["response"]) + assert_matches_type(FileDeleteResponse, file, path=["response"]) assert cast(Any, response.is_closed) is True @@ -203,9 +211,67 @@ def test_path_params_delete(self, client: Mixedbread) -> None: "", ) + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_content(self, client: Mixedbread, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + file = client.files.content( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert file.is_closed + assert file.json() == {"foo": "bar"} + assert cast(Any, file.is_closed) is True + assert isinstance(file, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_content(self, client: Mixedbread, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + file = client.files.with_raw_response.content( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert file.is_closed is True + assert file.http_request.headers.get("X-Stainless-Lang") == "python" + assert file.json() == {"foo": "bar"} + assert isinstance(file, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_content(self, client: Mixedbread, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + with client.files.with_streaming_response.content( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as file: + assert not file.is_closed + assert file.http_request.headers.get("X-Stainless-Lang") == "python" + + assert file.json() == {"foo": "bar"} + assert cast(Any, file.is_closed) is True + assert isinstance(file, StreamedBinaryAPIResponse) + + assert cast(Any, file.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_content(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.files.with_raw_response.content( + "", + ) + class TestAsyncFiles: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncMixedbread) -> None: @@ -321,15 +387,18 @@ async def test_path_params_update(self, async_client: AsyncMixedbread) -> None: @parametrize async def test_method_list(self, async_client: AsyncMixedbread) -> None: file = await async_client.files.list() - assert_matches_type(FileListResponse, file, path=["response"]) + assert_matches_type(AsyncCursor[FileObject], file, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: file = await async_client.files.list( - limit=0, - offset=0, + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + q="x", ) - assert_matches_type(FileListResponse, file, path=["response"]) + assert_matches_type(AsyncCursor[FileObject], file, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: @@ -338,7 +407,7 @@ async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(FileListResponse, file, path=["response"]) + assert_matches_type(AsyncCursor[FileObject], file, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: @@ -347,7 +416,7 @@ async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(FileListResponse, file, path=["response"]) + assert_matches_type(AsyncCursor[FileObject], file, path=["response"]) assert cast(Any, response.is_closed) is True @@ -356,7 +425,7 @@ async def test_method_delete(self, async_client: AsyncMixedbread) -> None: file = await async_client.files.delete( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(FileDeleted, file, path=["response"]) + assert_matches_type(FileDeleteResponse, file, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: @@ -367,7 +436,7 @@ async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(FileDeleted, file, path=["response"]) + assert_matches_type(FileDeleteResponse, file, path=["response"]) @parametrize async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: @@ -378,7 +447,7 @@ async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = await response.parse() - assert_matches_type(FileDeleted, file, path=["response"]) + assert_matches_type(FileDeleteResponse, file, path=["response"]) assert cast(Any, response.is_closed) is True @@ -388,3 +457,59 @@ async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: await async_client.files.with_raw_response.delete( "", ) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_content(self, async_client: AsyncMixedbread, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + file = await async_client.files.content( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert file.is_closed + assert await file.json() == {"foo": "bar"} + assert cast(Any, file.is_closed) is True + assert isinstance(file, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_content(self, async_client: AsyncMixedbread, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + file = await async_client.files.with_raw_response.content( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert file.is_closed is True + assert file.http_request.headers.get("X-Stainless-Lang") == "python" + assert await file.json() == {"foo": "bar"} + assert isinstance(file, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_content(self, async_client: AsyncMixedbread, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/files/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/content").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + async with async_client.files.with_streaming_response.content( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as file: + assert not file.is_closed + assert file.http_request.headers.get("X-Stainless-Lang") == "python" + + assert await file.json() == {"foo": "bar"} + assert cast(Any, file.is_closed) is True + assert isinstance(file, AsyncStreamedBinaryAPIResponse) + + assert cast(Any, file.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_content(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.files.with_raw_response.content( + "", + ) diff --git a/tests/api_resources/test_reranking.py b/tests/api_resources/test_reranking.py deleted file mode 100644 index 009148f5..00000000 --- a/tests/api_resources/test_reranking.py +++ /dev/null @@ -1,114 +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 mixedbread import Mixedbread, AsyncMixedbread -from tests.utils import assert_matches_type -from mixedbread.types import RerankingCreateResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestReranking: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_create(self, client: Mixedbread) -> None: - reranking = client.reranking.create( - input=["Document 1", "Document 2"], - query="What is mixedbread ai?", - ) - assert_matches_type(RerankingCreateResponse, reranking, path=["response"]) - - @parametrize - def test_method_create_with_all_params(self, client: Mixedbread) -> None: - reranking = client.reranking.create( - input=["Document 1", "Document 2"], - query="What is mixedbread ai?", - model="x", - rank_fields=["field1", "field2"], - return_input=False, - top_k=10, - ) - assert_matches_type(RerankingCreateResponse, reranking, path=["response"]) - - @parametrize - def test_raw_response_create(self, client: Mixedbread) -> None: - response = client.reranking.with_raw_response.create( - input=["Document 1", "Document 2"], - query="What is mixedbread ai?", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - reranking = response.parse() - assert_matches_type(RerankingCreateResponse, reranking, path=["response"]) - - @parametrize - def test_streaming_response_create(self, client: Mixedbread) -> None: - with client.reranking.with_streaming_response.create( - input=["Document 1", "Document 2"], - query="What is mixedbread ai?", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - reranking = response.parse() - assert_matches_type(RerankingCreateResponse, reranking, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncReranking: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_create(self, async_client: AsyncMixedbread) -> None: - reranking = await async_client.reranking.create( - input=["Document 1", "Document 2"], - query="What is mixedbread ai?", - ) - assert_matches_type(RerankingCreateResponse, reranking, path=["response"]) - - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: - reranking = await async_client.reranking.create( - input=["Document 1", "Document 2"], - query="What is mixedbread ai?", - model="x", - rank_fields=["field1", "field2"], - return_input=False, - top_k=10, - ) - assert_matches_type(RerankingCreateResponse, reranking, path=["response"]) - - @parametrize - async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: - response = await async_client.reranking.with_raw_response.create( - input=["Document 1", "Document 2"], - query="What is mixedbread ai?", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - reranking = await response.parse() - assert_matches_type(RerankingCreateResponse, reranking, path=["response"]) - - @parametrize - async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: - async with async_client.reranking.with_streaming_response.create( - input=["Document 1", "Document 2"], - query="What is mixedbread ai?", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - reranking = await response.parse() - assert_matches_type(RerankingCreateResponse, reranking, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_service_info.py b/tests/api_resources/test_service_info.py deleted file mode 100644 index 6bc48955..00000000 --- a/tests/api_resources/test_service_info.py +++ /dev/null @@ -1,72 +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 mixedbread import Mixedbread, AsyncMixedbread -from tests.utils import assert_matches_type -from mixedbread.types import InfoResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestServiceInfo: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_retrieve(self, client: Mixedbread) -> None: - service_info = client.service_info.retrieve() - assert_matches_type(InfoResponse, service_info, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Mixedbread) -> None: - response = client.service_info.with_raw_response.retrieve() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - service_info = response.parse() - assert_matches_type(InfoResponse, service_info, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Mixedbread) -> None: - with client.service_info.with_streaming_response.retrieve() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - service_info = response.parse() - assert_matches_type(InfoResponse, service_info, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncServiceInfo: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: - service_info = await async_client.service_info.retrieve() - assert_matches_type(InfoResponse, service_info, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: - response = await async_client.service_info.with_raw_response.retrieve() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - service_info = await response.parse() - assert_matches_type(InfoResponse, service_info, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: - async with async_client.service_info.with_streaming_response.retrieve() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - service_info = await response.parse() - assert_matches_type(InfoResponse, service_info, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_stores.py b/tests/api_resources/test_stores.py new file mode 100644 index 00000000..0a076705 --- /dev/null +++ b/tests/api_resources/test_stores.py @@ -0,0 +1,791 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.types import ( + Store, + StoreDeleteResponse, + StoreSearchResponse, + StoreMetadataFacetsResponse, + StoreQuestionAnsweringResponse, +) +from mixedbread.pagination import SyncCursor, AsyncCursor + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestStores: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Mixedbread) -> None: + store = client.stores.create() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.create( + name="technical-documentation", + description="Contains technical specifications and guides", + is_public=False, + expires_after={ + "anchor": "last_active_at", + "days": 0, + }, + metadata={}, + config={ + "contextualization": True, + "save_content": True, + }, + file_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Mixedbread) -> None: + store = client.stores.retrieve( + "store_identifier", + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.retrieve( + "store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.retrieve( + "store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Mixedbread) -> None: + store = client.stores.update( + store_identifier="store_identifier", + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.update( + store_identifier="store_identifier", + name="x", + description="description", + is_public=True, + expires_after={ + "anchor": "last_active_at", + "days": 0, + }, + metadata={}, + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.update( + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.update( + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.with_raw_response.update( + store_identifier="", + ) + + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + store = client.stores.list() + assert_matches_type(SyncCursor[Store], store, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.list( + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + q="x", + ) + assert_matches_type(SyncCursor[Store], store, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(SyncCursor[Store], store, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(SyncCursor[Store], store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Mixedbread) -> None: + store = client.stores.delete( + "store_identifier", + ) + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.delete( + "store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.delete( + "store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_metadata_facets(self, client: Mixedbread) -> None: + store = client.stores.metadata_facets( + store_identifiers=["string"], + ) + assert_matches_type(StoreMetadataFacetsResponse, store, path=["response"]) + + @parametrize + def test_method_metadata_facets_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.metadata_facets( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "agentic": True, + "return_metadata": True, + "apply_search_rules": True, + }, + facets=["string"], + ) + assert_matches_type(StoreMetadataFacetsResponse, store, path=["response"]) + + @parametrize + def test_raw_response_metadata_facets(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.metadata_facets( + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(StoreMetadataFacetsResponse, store, path=["response"]) + + @parametrize + def test_streaming_response_metadata_facets(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.metadata_facets( + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(StoreMetadataFacetsResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_question_answering(self, client: Mixedbread) -> None: + store = client.stores.question_answering( + store_identifiers=["string"], + ) + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + def test_method_question_answering_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.question_answering( + query="x", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "agentic": True, + "return_metadata": True, + "apply_search_rules": True, + }, + stream=True, + qa_options={ + "cite": True, + "multimodal": True, + }, + ) + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + def test_raw_response_question_answering(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.question_answering( + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + def test_streaming_response_question_answering(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.question_answering( + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_search(self, client: Mixedbread) -> None: + store = client.stores.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + def test_method_search_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.search( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "agentic": True, + "return_metadata": True, + "apply_search_rules": True, + }, + ) + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + def test_raw_response_search(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + def test_streaming_response_search(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncStores: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.create() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.create( + name="technical-documentation", + description="Contains technical specifications and guides", + is_public=False, + expires_after={ + "anchor": "last_active_at", + "days": 0, + }, + metadata={}, + config={ + "contextualization": True, + "save_content": True, + }, + file_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.retrieve( + "store_identifier", + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.retrieve( + "store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.retrieve( + "store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.update( + store_identifier="store_identifier", + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.update( + store_identifier="store_identifier", + name="x", + description="description", + is_public=True, + expires_after={ + "anchor": "last_active_at", + "days": 0, + }, + metadata={}, + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.update( + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.update( + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.with_raw_response.update( + store_identifier="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.list() + assert_matches_type(AsyncCursor[Store], store, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.list( + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + q="x", + ) + assert_matches_type(AsyncCursor[Store], store, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(AsyncCursor[Store], store, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(AsyncCursor[Store], store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.delete( + "store_identifier", + ) + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.delete( + "store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.delete( + "store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_metadata_facets(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.metadata_facets( + store_identifiers=["string"], + ) + assert_matches_type(StoreMetadataFacetsResponse, store, path=["response"]) + + @parametrize + async def test_method_metadata_facets_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.metadata_facets( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "agentic": True, + "return_metadata": True, + "apply_search_rules": True, + }, + facets=["string"], + ) + assert_matches_type(StoreMetadataFacetsResponse, store, path=["response"]) + + @parametrize + async def test_raw_response_metadata_facets(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.metadata_facets( + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(StoreMetadataFacetsResponse, store, path=["response"]) + + @parametrize + async def test_streaming_response_metadata_facets(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.metadata_facets( + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(StoreMetadataFacetsResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_question_answering(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.question_answering( + store_identifiers=["string"], + ) + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + async def test_method_question_answering_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.question_answering( + query="x", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "agentic": True, + "return_metadata": True, + "apply_search_rules": True, + }, + stream=True, + qa_options={ + "cite": True, + "multimodal": True, + }, + ) + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + async def test_raw_response_question_answering(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.question_answering( + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + async def test_streaming_response_question_answering(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.question_answering( + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_search(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + async def test_method_search_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.search( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [{}, {}], + "any": [{}, {}], + "none": [{}, {}], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "agentic": True, + "return_metadata": True, + "apply_search_rules": True, + }, + ) + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + async def test_raw_response_search(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + async def test_streaming_response_search(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_vector_stores.py b/tests/api_resources/test_vector_stores.py deleted file mode 100644 index 25798670..00000000 --- a/tests/api_resources/test_vector_stores.py +++ /dev/null @@ -1,621 +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 mixedbread import Mixedbread, AsyncMixedbread -from tests.utils import assert_matches_type -from mixedbread.types import ( - VectorStore, - VectorStoreDeleted, - VectorStoreListResponse, - VectorStoreSearchResponse, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestVectorStores: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_create(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.create() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - def test_method_create_with_all_params(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.create( - description="Contains technical specifications and guides", - expires_after={ - "anchor": "last_used_at", - "days": 0, - }, - file_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - metadata={}, - name="Technical Documentation", - ) - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - def test_raw_response_create(self, client: Mixedbread) -> None: - response = client.vector_stores.with_raw_response.create() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - def test_streaming_response_create(self, client: Mixedbread) -> None: - with client.vector_stores.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_method_retrieve(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Mixedbread) -> None: - response = client.vector_stores.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" - vector_store = response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Mixedbread) -> None: - with client.vector_stores.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" - - vector_store = response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve(self, client: Mixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.vector_stores.with_raw_response.retrieve( - "", - ) - - @parametrize - def test_method_update(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.update( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - def test_method_update_with_all_params(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.update( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - description="x", - expires_after={ - "anchor": "last_used_at", - "days": 0, - }, - metadata={}, - name="x", - ) - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - def test_raw_response_update(self, client: Mixedbread) -> None: - response = client.vector_stores.with_raw_response.update( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - def test_streaming_response_update(self, client: Mixedbread) -> None: - with client.vector_stores.with_streaming_response.update( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_update(self, client: Mixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.vector_stores.with_raw_response.update( - vector_store_id="", - ) - - @parametrize - def test_method_list(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.list() - assert_matches_type(VectorStoreListResponse, vector_store, path=["response"]) - - @parametrize - def test_method_list_with_all_params(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.list( - limit=0, - offset=0, - ) - assert_matches_type(VectorStoreListResponse, vector_store, path=["response"]) - - @parametrize - def test_raw_response_list(self, client: Mixedbread) -> None: - response = client.vector_stores.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = response.parse() - assert_matches_type(VectorStoreListResponse, vector_store, path=["response"]) - - @parametrize - def test_streaming_response_list(self, client: Mixedbread) -> None: - with client.vector_stores.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = response.parse() - assert_matches_type(VectorStoreListResponse, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_method_delete(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.delete( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStoreDeleted, vector_store, path=["response"]) - - @parametrize - def test_raw_response_delete(self, client: Mixedbread) -> None: - response = client.vector_stores.with_raw_response.delete( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = response.parse() - assert_matches_type(VectorStoreDeleted, vector_store, path=["response"]) - - @parametrize - def test_streaming_response_delete(self, client: Mixedbread) -> None: - with client.vector_stores.with_streaming_response.delete( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = response.parse() - assert_matches_type(VectorStoreDeleted, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_delete(self, client: Mixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.vector_stores.with_raw_response.delete( - "", - ) - - @parametrize - def test_method_question_answering(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.question_answering( - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - assert_matches_type(object, vector_store, path=["response"]) - - @parametrize - def test_method_question_answering_with_all_params(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.question_answering( - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - qa_options={"cite": True}, - query="x", - search_options={ - "return_chunks": True, - "return_metadata": True, - "rewrite_query": True, - "score_threshold": 0, - }, - stream=True, - top_k=1, - ) - assert_matches_type(object, vector_store, path=["response"]) - - @parametrize - def test_raw_response_question_answering(self, client: Mixedbread) -> None: - response = client.vector_stores.with_raw_response.question_answering( - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = response.parse() - assert_matches_type(object, vector_store, path=["response"]) - - @parametrize - def test_streaming_response_question_answering(self, client: Mixedbread) -> None: - with client.vector_stores.with_streaming_response.question_answering( - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = response.parse() - assert_matches_type(object, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_method_search(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - assert_matches_type(VectorStoreSearchResponse, vector_store, path=["response"]) - - @parametrize - def test_method_search_with_all_params(self, client: Mixedbread) -> None: - vector_store = client.vector_stores.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - search_options={ - "return_chunks": True, - "return_metadata": True, - "rewrite_query": True, - "score_threshold": 0, - }, - top_k=1, - ) - assert_matches_type(VectorStoreSearchResponse, vector_store, path=["response"]) - - @parametrize - def test_raw_response_search(self, client: Mixedbread) -> None: - response = client.vector_stores.with_raw_response.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = response.parse() - assert_matches_type(VectorStoreSearchResponse, vector_store, path=["response"]) - - @parametrize - def test_streaming_response_search(self, client: Mixedbread) -> None: - with client.vector_stores.with_streaming_response.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = response.parse() - assert_matches_type(VectorStoreSearchResponse, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncVectorStores: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_create(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.create() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.create( - description="Contains technical specifications and guides", - expires_after={ - "anchor": "last_used_at", - "days": 0, - }, - file_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - metadata={}, - name="Technical Documentation", - ) - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.with_raw_response.create() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = await response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = await response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.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" - vector_store = await response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.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" - - vector_store = await response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.vector_stores.with_raw_response.retrieve( - "", - ) - - @parametrize - async def test_method_update(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.update( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.update( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - description="x", - expires_after={ - "anchor": "last_used_at", - "days": 0, - }, - metadata={}, - name="x", - ) - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - async def test_raw_response_update(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.with_raw_response.update( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = await response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - @parametrize - async def test_streaming_response_update(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.with_streaming_response.update( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = await response.parse() - assert_matches_type(VectorStore, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_update(self, async_client: AsyncMixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.vector_stores.with_raw_response.update( - vector_store_id="", - ) - - @parametrize - async def test_method_list(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.list() - assert_matches_type(VectorStoreListResponse, vector_store, path=["response"]) - - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.list( - limit=0, - offset=0, - ) - assert_matches_type(VectorStoreListResponse, vector_store, path=["response"]) - - @parametrize - async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = await response.parse() - assert_matches_type(VectorStoreListResponse, vector_store, path=["response"]) - - @parametrize - async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = await response.parse() - assert_matches_type(VectorStoreListResponse, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_delete(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.delete( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStoreDeleted, vector_store, path=["response"]) - - @parametrize - async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.with_raw_response.delete( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = await response.parse() - assert_matches_type(VectorStoreDeleted, vector_store, path=["response"]) - - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.with_streaming_response.delete( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = await response.parse() - assert_matches_type(VectorStoreDeleted, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.vector_stores.with_raw_response.delete( - "", - ) - - @parametrize - async def test_method_question_answering(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.question_answering( - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - assert_matches_type(object, vector_store, path=["response"]) - - @parametrize - async def test_method_question_answering_with_all_params(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.question_answering( - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - qa_options={"cite": True}, - query="x", - search_options={ - "return_chunks": True, - "return_metadata": True, - "rewrite_query": True, - "score_threshold": 0, - }, - stream=True, - top_k=1, - ) - assert_matches_type(object, vector_store, path=["response"]) - - @parametrize - async def test_raw_response_question_answering(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.with_raw_response.question_answering( - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = await response.parse() - assert_matches_type(object, vector_store, path=["response"]) - - @parametrize - async def test_streaming_response_question_answering(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.with_streaming_response.question_answering( - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = await response.parse() - assert_matches_type(object, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_search(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - assert_matches_type(VectorStoreSearchResponse, vector_store, path=["response"]) - - @parametrize - async def test_method_search_with_all_params(self, async_client: AsyncMixedbread) -> None: - vector_store = await async_client.vector_stores.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - search_options={ - "return_chunks": True, - "return_metadata": True, - "rewrite_query": True, - "score_threshold": 0, - }, - top_k=1, - ) - assert_matches_type(VectorStoreSearchResponse, vector_store, path=["response"]) - - @parametrize - async def test_raw_response_search(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.with_raw_response.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - vector_store = await response.parse() - assert_matches_type(VectorStoreSearchResponse, vector_store, path=["response"]) - - @parametrize - async def test_streaming_response_search(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.with_streaming_response.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - vector_store = await response.parse() - assert_matches_type(VectorStoreSearchResponse, vector_store, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/vector_stores/test_files.py b/tests/api_resources/vector_stores/test_files.py deleted file mode 100644 index c3900a06..00000000 --- a/tests/api_resources/vector_stores/test_files.py +++ /dev/null @@ -1,513 +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 mixedbread import Mixedbread, AsyncMixedbread -from tests.utils import assert_matches_type -from mixedbread.types.vector_stores import ( - VectorStoreFile, - FileListResponse, - FileSearchResponse, - VectorStoreFileDeleted, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestFiles: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_create(self, client: Mixedbread) -> None: - file = client.vector_stores.files.create( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - def test_method_create_with_all_params(self, client: Mixedbread) -> None: - file = client.vector_stores.files.create( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - metadata={}, - ) - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - def test_raw_response_create(self, client: Mixedbread) -> None: - response = client.vector_stores.files.with_raw_response.create( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - def test_streaming_response_create(self, client: Mixedbread) -> None: - with client.vector_stores.files.with_streaming_response.create( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_create(self, client: Mixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.vector_stores.files.with_raw_response.create( - vector_store_id="", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @parametrize - def test_method_retrieve(self, client: Mixedbread) -> None: - file = client.vector_stores.files.retrieve( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Mixedbread) -> None: - response = client.vector_stores.files.with_raw_response.retrieve( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Mixedbread) -> None: - with client.vector_stores.files.with_streaming_response.retrieve( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve(self, client: Mixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.vector_stores.files.with_raw_response.retrieve( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - client.vector_stores.files.with_raw_response.retrieve( - file_id="", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @parametrize - def test_method_list(self, client: Mixedbread) -> None: - file = client.vector_stores.files.list( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(FileListResponse, file, path=["response"]) - - @parametrize - def test_method_list_with_all_params(self, client: Mixedbread) -> None: - file = client.vector_stores.files.list( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - limit=0, - offset=0, - ) - assert_matches_type(FileListResponse, file, path=["response"]) - - @parametrize - def test_raw_response_list(self, client: Mixedbread) -> None: - response = client.vector_stores.files.with_raw_response.list( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = response.parse() - assert_matches_type(FileListResponse, file, path=["response"]) - - @parametrize - def test_streaming_response_list(self, client: Mixedbread) -> None: - with client.vector_stores.files.with_streaming_response.list( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = response.parse() - assert_matches_type(FileListResponse, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_list(self, client: Mixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.vector_stores.files.with_raw_response.list( - vector_store_id="", - ) - - @parametrize - def test_method_delete(self, client: Mixedbread) -> None: - file = client.vector_stores.files.delete( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) - - @parametrize - def test_raw_response_delete(self, client: Mixedbread) -> None: - response = client.vector_stores.files.with_raw_response.delete( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = response.parse() - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) - - @parametrize - def test_streaming_response_delete(self, client: Mixedbread) -> None: - with client.vector_stores.files.with_streaming_response.delete( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = response.parse() - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_delete(self, client: Mixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - client.vector_stores.files.with_raw_response.delete( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - client.vector_stores.files.with_raw_response.delete( - file_id="", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @parametrize - def test_method_search(self, client: Mixedbread) -> None: - file = client.vector_stores.files.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - assert_matches_type(FileSearchResponse, file, path=["response"]) - - @parametrize - def test_method_search_with_all_params(self, client: Mixedbread) -> None: - file = client.vector_stores.files.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - search_options={ - "return_chunks": True, - "return_metadata": True, - "rewrite_query": True, - "score_threshold": 0, - }, - top_k=1, - ) - assert_matches_type(FileSearchResponse, file, path=["response"]) - - @parametrize - def test_raw_response_search(self, client: Mixedbread) -> None: - response = client.vector_stores.files.with_raw_response.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = response.parse() - assert_matches_type(FileSearchResponse, file, path=["response"]) - - @parametrize - def test_streaming_response_search(self, client: Mixedbread) -> None: - with client.vector_stores.files.with_streaming_response.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = response.parse() - assert_matches_type(FileSearchResponse, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncFiles: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_create(self, async_client: AsyncMixedbread) -> None: - file = await async_client.vector_stores.files.create( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: - file = await async_client.vector_stores.files.create( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - metadata={}, - ) - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.files.with_raw_response.create( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = await response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.files.with_streaming_response.create( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = await response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_create(self, async_client: AsyncMixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.vector_stores.files.with_raw_response.create( - vector_store_id="", - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @parametrize - async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: - file = await async_client.vector_stores.files.retrieve( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.files.with_raw_response.retrieve( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = await response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.files.with_streaming_response.retrieve( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = await response.parse() - assert_matches_type(VectorStoreFile, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.vector_stores.files.with_raw_response.retrieve( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - await async_client.vector_stores.files.with_raw_response.retrieve( - file_id="", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @parametrize - async def test_method_list(self, async_client: AsyncMixedbread) -> None: - file = await async_client.vector_stores.files.list( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(FileListResponse, file, path=["response"]) - - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: - file = await async_client.vector_stores.files.list( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - limit=0, - offset=0, - ) - assert_matches_type(FileListResponse, file, path=["response"]) - - @parametrize - async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.files.with_raw_response.list( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = await response.parse() - assert_matches_type(FileListResponse, file, path=["response"]) - - @parametrize - async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.files.with_streaming_response.list( - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = await response.parse() - assert_matches_type(FileListResponse, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_list(self, async_client: AsyncMixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.vector_stores.files.with_raw_response.list( - vector_store_id="", - ) - - @parametrize - async def test_method_delete(self, async_client: AsyncMixedbread) -> None: - file = await async_client.vector_stores.files.delete( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) - - @parametrize - async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.files.with_raw_response.delete( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = await response.parse() - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) - - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.files.with_streaming_response.delete( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = await response.parse() - assert_matches_type(VectorStoreFileDeleted, file, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `vector_store_id` but received ''"): - await async_client.vector_stores.files.with_raw_response.delete( - file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - vector_store_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): - await async_client.vector_stores.files.with_raw_response.delete( - file_id="", - vector_store_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @parametrize - async def test_method_search(self, async_client: AsyncMixedbread) -> None: - file = await async_client.vector_stores.files.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - assert_matches_type(FileSearchResponse, file, path=["response"]) - - @parametrize - async def test_method_search_with_all_params(self, async_client: AsyncMixedbread) -> None: - file = await async_client.vector_stores.files.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - search_options={ - "return_chunks": True, - "return_metadata": True, - "rewrite_query": True, - "score_threshold": 0, - }, - top_k=1, - ) - assert_matches_type(FileSearchResponse, file, path=["response"]) - - @parametrize - async def test_raw_response_search(self, async_client: AsyncMixedbread) -> None: - response = await async_client.vector_stores.files.with_raw_response.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - file = await response.parse() - assert_matches_type(FileSearchResponse, file, path=["response"]) - - @parametrize - async def test_streaming_response_search(self, async_client: AsyncMixedbread) -> None: - async with async_client.vector_stores.files.with_streaming_response.search( - query="how to configure SSL", - vector_store_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - file = await response.parse() - assert_matches_type(FileSearchResponse, file, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/conftest.py b/tests/conftest.py index f546bdd5..9b35601c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,16 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from mixedbread import Mixedbread, AsyncMixedbread +from mixedbread import Mixedbread, AsyncMixedbread, DefaultAioHttpClient +from mixedbread._utils import is_dict if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest + from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] pytest.register_assert_rewrite("tests.utils") @@ -25,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -43,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[Mixedbread]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncMixedbread]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") + + async with AsyncMixedbread( + base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client + ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 5c2e3f24..8cc00170 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -8,12 +8,11 @@ import json import asyncio import inspect -import subprocess +import dataclasses import tracemalloc -from typing import Any, Union, cast -from textwrap import dedent +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock -from typing_extensions import Literal +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest @@ -22,18 +21,23 @@ from mixedbread import Mixedbread, AsyncMixedbread, APIResponseValidationError from mixedbread._types import Omit +from mixedbread._utils import asyncify from mixedbread._models import BaseModel, FinalRequestOptions -from mixedbread._constants import RAW_RESPONSE_HEADER -from mixedbread._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError +from mixedbread._exceptions import APIStatusError, APITimeoutError, MixedbreadError, APIResponseValidationError from mixedbread._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + OtherPlatform, + DefaultHttpxClient, + DefaultAsyncHttpxClient, + get_platform, make_request_options, ) from .utils import update_env +T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "My API Key" @@ -48,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: Mixedbread | AsyncMixedbread) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -57,51 +112,49 @@ def _get_open_connections(client: Mixedbread | AsyncMixedbread) -> int: class TestMixedbread: - client = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) - @pytest.mark.respx(base_url=base_url) - def test_raw_response(self, respx_mock: MockRouter) -> None: + def test_raw_response(self, respx_mock: MockRouter, client: Mixedbread) -> 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: Mixedbread) -> 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: Mixedbread) -> None: + copied = client.copy() + assert id(copied) != id(client) - copied = self.client.copy(api_key="another My API Key") + copied = client.copy(api_key="another My API Key") assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + assert client.api_key == "My API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, client: Mixedbread) -> 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 = Mixedbread( @@ -136,6 +189,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 = Mixedbread( @@ -173,13 +227,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: Mixedbread) -> 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(): @@ -189,12 +245,13 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" - def test_copy_build_request(self) -> None: + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") + def test_copy_build_request(self, client: Mixedbread) -> 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) @@ -251,14 +308,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: Mixedbread) -> 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) @@ -271,6 +326,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: @@ -282,6 +339,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 = Mixedbread( @@ -292,6 +351,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 = Mixedbread( @@ -302,6 +363,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: @@ -313,14 +376,14 @@ async def test_invalid_http_client(self) -> None: ) def test_default_headers_option(self) -> None: - client = Mixedbread( + test_client = Mixedbread( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = Mixedbread( + test_client2 = Mixedbread( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -329,10 +392,23 @@ def test_default_headers_option(self) -> None: "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + test_client.close() + test_client2.close() + + def test_validate_headers(self) -> None: + client = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + assert request.headers.get("Authorization") == f"Bearer {api_key}" + + with pytest.raises(MixedbreadError): + with update_env(**{"MXBAI_API_KEY": Omit()}): + client2 = Mixedbread(base_url=base_url, api_key=None, _strict_response_validation=True) + _ = client2 + def test_default_query_option(self) -> None: client = Mixedbread( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} @@ -351,8 +427,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: Mixedbread) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -363,7 +441,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", @@ -374,7 +452,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", @@ -385,8 +463,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: Mixedbread) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -396,7 +474,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", @@ -407,8 +485,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: Mixedbread) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -421,7 +499,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", @@ -435,7 +513,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", @@ -451,7 +529,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: Mixedbread) -> 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"]}, @@ -478,7 +556,71 @@ def test_multipart_repeating_array(self, client: Mixedbread) -> None: ] @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter) -> None: + def test_binary_content_upload(self, respx_mock: MockRouter, client: Mixedbread) -> 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 Mixedbread( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + ) as client: + response = client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Mixedbread) -> 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: Mixedbread) -> None: class Model1(BaseModel): name: str @@ -487,12 +629,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: Mixedbread) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -503,18 +645,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: Mixedbread) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -530,7 +672,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 @@ -542,6 +684,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(MIXEDBREAD_BASE_URL="http://localhost:5000/from/env"): client = Mixedbread(api_key=api_key, _strict_response_validation=True) @@ -555,7 +699,9 @@ def test_base_url_env(self) -> None: client = Mixedbread( base_url=None, api_key=api_key, _strict_response_validation=True, environment="production" ) - assert str(client.base_url).startswith("https://api.mixedbread.ai") + assert str(client.base_url).startswith("https://api.mixedbread.com") + + client.close() @pytest.mark.parametrize( "client", @@ -581,6 +727,7 @@ def test_base_url_trailing_slash(self, client: Mixedbread) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -606,6 +753,7 @@ def test_base_url_no_trailing_slash(self, client: Mixedbread) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -631,35 +779,36 @@ def test_absolute_request_url(self, client: Mixedbread) -> None: ), ) assert request.url == "https://myapi.com/foo" + client.close() def test_copied_client_does_not_close_http(self) -> None: - client = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied - assert not client.is_closed() + assert not test_client.is_closed() def test_client_context_manager(self) -> None: - client = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) - with client as c2: - assert c2 is client + test_client = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) + with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + def test_client_response_validation_error(self, respx_mock: MockRouter, client: Mixedbread) -> 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) @@ -681,11 +830,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=False) - response = client.get("/foo", cast_to=Model) + response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + strict_client.close() + non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -708,9 +860,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 = Mixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, client: Mixedbread + ) -> 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) @@ -718,33 +870,22 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("mixedbread._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("/v1/files").mock(side_effect=httpx.TimeoutException("Test timeout error")) + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Mixedbread) -> None: + respx_mock.post("/v1/stores").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.post( - "/v1/files", - body=cast(object, dict(file=b"raw file contents")), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.stores.with_streaming_response.create().__enter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(client) == 0 @mock.patch("mixedbread._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("/v1/files").mock(return_value=httpx.Response(500)) + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Mixedbread) -> None: + respx_mock.post("/v1/stores").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.post( - "/v1/files", - body=cast(object, dict(file=b"raw file contents")), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - - assert _get_open_connections(self.client) == 0 + client.stores.with_streaming_response.create().__enter__() + assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("mixedbread._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @@ -770,9 +911,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/files").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = client.files.with_raw_response.create(file=b"raw file contents") + response = client.stores.with_raw_response.create() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -794,11 +935,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/files").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = client.files.with_raw_response.create( - file=b"raw file contents", extra_headers={"x-stainless-retry-count": Omit()} - ) + response = client.stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -819,65 +958,114 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/files").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = client.files.with_raw_response.create( - file=b"raw file contents", extra_headers={"x-stainless-retry-count": "42"} - ) + response = client.stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) -class TestAsyncMixedbread: - client = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects(self, respx_mock: MockRouter, client: Mixedbread) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: Mixedbread) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" + +class TestAsyncMixedbread: @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: AsyncMixedbread) -> 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: AsyncMixedbread) -> 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: AsyncMixedbread) -> None: + copied = async_client.copy() + assert id(copied) != id(async_client) - copied = self.client.copy(api_key="another My API Key") + copied = async_client.copy(api_key="another My API Key") assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + assert async_client.api_key == "My API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, async_client: AsyncMixedbread) -> 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 = AsyncMixedbread( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) @@ -910,8 +1098,9 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + await client.close() - def test_copy_default_query(self) -> None: + async def test_copy_default_query(self) -> None: client = AsyncMixedbread( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) @@ -947,13 +1136,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: AsyncMixedbread) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + async_client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(async_client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -963,12 +1154,13 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" - def test_copy_build_request(self) -> None: + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") + def test_copy_build_request(self, async_client: AsyncMixedbread) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = async_client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -1025,12 +1217,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: AsyncMixedbread) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( + request = async_client._build_request( FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) ) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1045,6 +1237,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: @@ -1056,6 +1250,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 = AsyncMixedbread( @@ -1066,6 +1262,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 = AsyncMixedbread( @@ -1076,6 +1274,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: @@ -1086,15 +1286,15 @@ def test_invalid_http_client(self) -> None: http_client=cast(Any, http_client), ) - def test_default_headers_option(self) -> None: - client = AsyncMixedbread( + async def test_default_headers_option(self) -> None: + test_client = AsyncMixedbread( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncMixedbread( + test_client2 = AsyncMixedbread( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -1103,11 +1303,24 @@ 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() + + def test_validate_headers(self) -> None: + client = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + assert request.headers.get("Authorization") == f"Bearer {api_key}" + + with pytest.raises(MixedbreadError): + with update_env(**{"MXBAI_API_KEY": Omit()}): + client2 = AsyncMixedbread(base_url=base_url, api_key=None, _strict_response_validation=True) + _ = client2 + + async def test_default_query_option(self) -> None: client = AsyncMixedbread( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) @@ -1125,8 +1338,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: Mixedbread) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1137,7 +1352,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", @@ -1148,7 +1363,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", @@ -1159,8 +1374,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: Mixedbread) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1170,7 +1385,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", @@ -1181,8 +1396,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: Mixedbread) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1195,7 +1410,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", @@ -1209,7 +1424,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", @@ -1225,7 +1440,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncMixedbread) -> 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"]}, @@ -1252,7 +1467,73 @@ def test_multipart_repeating_array(self, async_client: AsyncMixedbread) -> None: ] @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter) -> None: + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncMixedbread) -> 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 AsyncMixedbread( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + ) as client: + response = await client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload_with_body_is_deprecated( + self, respx_mock: MockRouter, async_client: AsyncMixedbread + ) -> 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: AsyncMixedbread) -> None: class Model1(BaseModel): name: str @@ -1261,12 +1542,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: AsyncMixedbread) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -1277,18 +1558,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: AsyncMixedbread + ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -1304,11 +1587,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 = AsyncMixedbread( base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True ) @@ -1318,7 +1601,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(MIXEDBREAD_BASE_URL="http://localhost:5000/from/env"): client = AsyncMixedbread(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @@ -1331,7 +1616,9 @@ def test_base_url_env(self) -> None: client = AsyncMixedbread( base_url=None, api_key=api_key, _strict_response_validation=True, environment="production" ) - assert str(client.base_url).startswith("https://api.mixedbread.ai") + assert str(client.base_url).startswith("https://api.mixedbread.com") + + await client.close() @pytest.mark.parametrize( "client", @@ -1348,7 +1635,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncMixedbread) -> None: + async def test_base_url_trailing_slash(self, client: AsyncMixedbread) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1357,6 +1644,7 @@ def test_base_url_trailing_slash(self, client: AsyncMixedbread) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1373,7 +1661,7 @@ def test_base_url_trailing_slash(self, client: AsyncMixedbread) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncMixedbread) -> None: + async def test_base_url_no_trailing_slash(self, client: AsyncMixedbread) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1382,6 +1670,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncMixedbread) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1398,7 +1687,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncMixedbread) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncMixedbread) -> None: + async def test_absolute_request_url(self, client: AsyncMixedbread) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1407,37 +1696,39 @@ def test_absolute_request_url(self, client: AsyncMixedbread) -> None: ), ) assert request.url == "https://myapi.com/foo" + await client.close() async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied await asyncio.sleep(0.2) - assert not client.is_closed() + assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) - async with client as c2: - assert c2 is client + test_client = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) + async with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + async def test_client_response_validation_error( + self, respx_mock: MockRouter, async_client: AsyncMixedbread + ) -> 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) @@ -1448,7 +1739,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 @@ -1460,11 +1750,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=False) - response = await client.get("/foo", cast_to=Model) + response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + await strict_client.close() + await non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -1487,49 +1780,40 @@ 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 = AsyncMixedbread(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + async def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncMixedbread + ) -> 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("mixedbread._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("/v1/files").mock(side_effect=httpx.TimeoutException("Test timeout error")) + async def test_retrying_timeout_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncMixedbread + ) -> None: + respx_mock.post("/v1/stores").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.post( - "/v1/files", - body=cast(object, dict(file=b"raw file contents")), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + await async_client.stores.with_streaming_response.create().__aenter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(async_client) == 0 @mock.patch("mixedbread._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("/v1/files").mock(return_value=httpx.Response(500)) + async def test_retrying_status_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncMixedbread + ) -> None: + respx_mock.post("/v1/stores").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.post( - "/v1/files", - body=cast(object, dict(file=b"raw file contents")), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - - assert _get_open_connections(self.client) == 0 + await async_client.stores.with_streaming_response.create().__aenter__() + assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("mixedbread._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, @@ -1551,9 +1835,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/files").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = await client.files.with_raw_response.create(file=b"raw file contents") + response = await client.stores.with_raw_response.create() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1561,7 +1845,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("mixedbread._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: AsyncMixedbread, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1576,18 +1859,15 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/files").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = await client.files.with_raw_response.create( - file=b"raw file contents", extra_headers={"x-stainless-retry-count": Omit()} - ) + response = await client.stores.with_raw_response.create(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("mixedbread._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: AsyncMixedbread, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1602,45 +1882,69 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/files").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = await client.files.with_raw_response.create( - file=b"raw file contents", extra_headers={"x-stainless-retry-count": "42"} - ) + response = await client.stores.with_raw_response.create(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 mixedbread._utils import asyncify - from mixedbread._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 + async def test_get_platform(self) -> None: + platform = await asyncify(get_platform)() + assert isinstance(platform, (str, OtherPlatform)) + + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncMixedbread) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncMixedbread) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + await async_client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" diff --git a/tests/test_models.py b/tests/test_models.py index 0176e2e8..a8c78064 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone from typing_extensions import Literal, Annotated, TypeAliasType @@ -8,8 +8,8 @@ from pydantic import Field from mixedbread._utils import PropertyInfo -from mixedbread._compat import PYDANTIC_V2, parse_obj, model_dump, model_json -from mixedbread._models import BaseModel, construct_type +from mixedbread._compat import PYDANTIC_V1, parse_obj, model_dump, model_json +from mixedbread._models import DISCRIMINATOR_CACHE, BaseModel, construct_type class BasicModel(BaseModel): @@ -294,12 +294,12 @@ class Model(BaseModel): assert cast(bool, m.foo) is True m = Model.construct(foo={"name": 3}) - if PYDANTIC_V2: - assert isinstance(m.foo, Submodel1) - assert m.foo.name == 3 # type: ignore - else: + if PYDANTIC_V1: assert isinstance(m.foo, Submodel2) assert m.foo.name == "3" + else: + assert isinstance(m.foo, Submodel1) + assert m.foo.name == 3 # type: ignore def test_list_of_unions() -> None: @@ -426,10 +426,10 @@ class Model(BaseModel): expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc) - if PYDANTIC_V2: - expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' - else: + if PYDANTIC_V1: expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' + else: + expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' model = Model.construct(created_at="2019-12-27T18:11:19.117Z") assert model.created_at == expected @@ -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 @@ -528,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) @@ -553,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) @@ -577,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()) == {} @@ -592,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) @@ -619,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) @@ -676,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: @@ -765,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: @@ -806,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")]) @@ -815,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( @@ -827,12 +830,12 @@ class B(BaseModel): # if the discriminator details object stays the same between invocations then # we hit the cache - assert UnionType.__discriminator__ is discriminator + assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: - Alias = TypeAliasType("Alias", str) + Alias = TypeAliasType("Alias", str) # pyright: ignore class Model(BaseModel): alias: Alias @@ -846,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 @@ -854,3 +857,107 @@ 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) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2) + + +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2 for now") +def test_extra_properties() -> None: + class Item(BaseModel): + prop: int + + class Model(BaseModel): + __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + other: str + + if TYPE_CHECKING: + + def __getattr__(self, attr: str) -> Item: ... + + model = construct_type( + type_=Model, + value={ + "a": {"prop": 1}, + "other": "foo", + }, + ) + assert isinstance(model, Model) + assert model.a.prop == 1 + assert isinstance(model.a, Item) + assert model.other == "foo" diff --git a/tests/test_transform.py b/tests/test_transform.py index 67fec2f4..1337f4f6 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -2,20 +2,20 @@ import io import pathlib -from typing import Any, List, Union, TypeVar, Iterable, Optional, cast +from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast from datetime import date, datetime from typing_extensions import Required, Annotated, TypedDict import pytest -from mixedbread._types import Base64FileInput +from mixedbread._types import Base64FileInput, omit, not_given from mixedbread._utils import ( PropertyInfo, transform as _transform, parse_datetime, async_transform as _async_transform, ) -from mixedbread._compat import PYDANTIC_V2 +from mixedbread._compat import PYDANTIC_V1 from mixedbread._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"}} @@ -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")] @@ -423,3 +432,29 @@ async def test_base64_file_input(use_async: bool) -> None: assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { "foo": "SGVsbG8sIHdvcmxkIQ==" } # type: ignore[comparison-overlap] + + +@parametrize +@pytest.mark.asyncio +async def test_transform_skipping(use_async: bool) -> None: + # lists of ints are left as-is + data = [1, 2, 3] + assert await transform(data, List[int], use_async) is data + + # iterables of ints are converted to a list + data = iter([1, 2, 3]) + assert await transform(data, Iterable[int], use_async) == [1, 2, 3] + + +@parametrize +@pytest.mark.asyncio +async def test_strips_notgiven(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_strips_omit(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": omit}, Foo1, use_async) == {} diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py new file mode 100644 index 00000000..27b7a48d --- /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 mixedbread._utils import parse_date, parse_datetime + + +def create_tz(minutes: int) -> timezone: + return timezone(timedelta(minutes=minutes)) + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + ("1494012444.883309", date(2017, 5, 5)), + (b"1494012444.883309", date(2017, 5, 5)), + (1_494_012_444.883_309, date(2017, 5, 5)), + ("1494012444", date(2017, 5, 5)), + (1_494_012_444, date(2017, 5, 5)), + (0, date(1970, 1, 1)), + ("2012-04-23", date(2012, 4, 23)), + (b"2012-04-23", date(2012, 4, 23)), + ("2012-4-9", date(2012, 4, 9)), + (date(2012, 4, 9), date(2012, 4, 9)), + (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)), + # Invalid inputs + ("x20120423", ValueError), + ("2012-04-56", ValueError), + (19_999_999_999, date(2603, 10, 11)), # just before watershed + (20_000_000_001, date(1970, 8, 20)), # just after watershed + (1_549_316_052, date(2019, 2, 4)), # nowish in s + (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms + (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs + (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns + ("infinity", date(9999, 12, 31)), + ("inf", date(9999, 12, 31)), + (float("inf"), date(9999, 12, 31)), + ("infinity ", date(9999, 12, 31)), + (int("1" + "0" * 100), date(9999, 12, 31)), + (1e1000, date(9999, 12, 31)), + ("-infinity", date(1, 1, 1)), + ("-inf", date(1, 1, 1)), + ("nan", ValueError), + ], +) +def test_date_parsing(value: Union[str, bytes, int, float], result: Union[date, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_date(value) + else: + assert parse_date(value) == result + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + # values in seconds + ("1494012444.883309", datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + ("1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (b"1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + # values in ms + ("1494012444000.883309", datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)), + ("-1494012444000.883309", datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)), + (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + ("2012-04-23T09:15:00", datetime(2012, 4, 23, 9, 15)), + ("2012-4-9 4:8:16", datetime(2012, 4, 9, 4, 8, 16)), + ("2012-04-23T09:15:00Z", datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)), + ("2012-4-9 4:8:16-0320", datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))), + ("2012-04-23T10:20:30.400+02:30", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))), + ("2012-04-23T10:20:30.400+02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))), + ("2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (b"2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (datetime(2017, 5, 5), datetime(2017, 5, 5)), + (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)), + # Invalid inputs + ("x20120423091500", ValueError), + ("2012-04-56T09:15:90", ValueError), + ("2012-04-23T11:05:00-25:00", ValueError), + (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed + (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed + (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s + (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms + (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs + (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns + ("infinity", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf ", datetime(9999, 12, 31, 23, 59, 59, 999999)), + (1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)), + (float("inf"), datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("-infinity", datetime(1, 1, 1, 0, 0)), + ("-inf", datetime(1, 1, 1, 0, 0)), + ("nan", ValueError), + ], +) +def test_datetime_parsing(value: Union[str, bytes, int, float], result: Union[datetime, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_datetime(value) + else: + assert parse_datetime(value) == result diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py new file mode 100644 index 00000000..732c37f3 --- /dev/null +++ b/tests/test_utils/test_json.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import datetime +from typing import Union + +import pydantic + +from mixedbread import _compat +from mixedbread._utils._json import openapi_dumps + + +class TestOpenapiDumps: + def test_basic(self) -> None: + data = {"key": "value", "number": 42} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"key":"value","number":42}' + + def test_datetime_serialization(self) -> None: + dt = datetime.datetime(2023, 1, 1, 12, 0, 0) + data = {"datetime": dt} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' + + def test_pydantic_model_serialization(self) -> None: + class User(pydantic.BaseModel): + first_name: str + last_name: str + age: int + + model_instance = User(first_name="John", last_name="Kramer", age=83) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' + + def test_pydantic_model_with_default_values(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + score: int = 0 + + model_instance = User(name="Alice") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Alice"}}' + + def test_pydantic_model_with_default_values_overridden(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + + model_instance = User(name="Bob", role="admin", active=False) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' + + def test_pydantic_model_with_alias(self) -> None: + class User(pydantic.BaseModel): + first_name: str = pydantic.Field(alias="firstName") + last_name: str = pydantic.Field(alias="lastName") + + model_instance = User(firstName="John", lastName="Doe") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' + + def test_pydantic_model_with_alias_and_default(self) -> None: + class User(pydantic.BaseModel): + user_name: str = pydantic.Field(alias="userName") + user_role: str = pydantic.Field(default="member", alias="userRole") + is_active: bool = pydantic.Field(default=True, alias="isActive") + + model_instance = User(userName="charlie") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"charlie"}}' + + model_with_overrides = User(userName="diana", userRole="admin", isActive=False) + data = {"model": model_with_overrides} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' + + def test_pydantic_model_with_nested_models_and_defaults(self) -> None: + class Address(pydantic.BaseModel): + street: str + city: str = "Unknown" + + class User(pydantic.BaseModel): + name: str + address: Address + verified: bool = False + + if _compat.PYDANTIC_V1: + # to handle forward references in Pydantic v1 + User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] + + address = Address(street="123 Main St") + user = User(name="Diana", address=address) + data = {"user": user} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' + + address_with_city = Address(street="456 Oak Ave", city="Boston") + user_verified = User(name="Eve", address=address_with_city, verified=True) + data = {"user": user_verified} + json_bytes = openapi_dumps(data) + assert ( + json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' + ) + + def test_pydantic_model_with_optional_fields(self) -> None: + class User(pydantic.BaseModel): + name: str + email: Union[str, None] + phone: Union[str, None] + + model_with_none = User(name="Eve", email=None, phone=None) + data = {"model": model_with_none} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' + + model_with_values = User(name="Frank", email="frank@example.com", phone=None) + data = {"model": model_with_values} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}' diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index 9b54af18..76104cef 100644 --- a/tests/test_utils/test_proxy.py +++ b/tests/test_utils/test_proxy.py @@ -21,3 +21,14 @@ def test_recursive_proxy() -> None: assert dir(proxy) == [] assert type(proxy).__name__ == "RecursiveLazyProxy" assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" + + +def test_isinstance_does_not_error() -> None: + class AlwaysErrorProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + raise RuntimeError("Mocking missing dependency") + + proxy = AlwaysErrorProxy() + assert not isinstance(proxy, dict) + assert isinstance(proxy, LazyProxy) diff --git a/tests/utils.py b/tests/utils.py index bd4c1519..c9a2638f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,7 @@ import inspect import traceback import contextlib -from typing import Any, TypeVar, Iterator, cast +from typing import Any, TypeVar, Iterator, Sequence, cast from datetime import date, datetime from typing_extensions import Literal, get_args, get_origin, assert_type @@ -15,10 +15,11 @@ is_list_type, is_union_type, extract_type_arg, + is_sequence_type, is_annotated_type, is_type_alias_type, ) -from mixedbread._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from mixedbread._compat import PYDANTIC_V1, field_outer_type, get_model_fields from mixedbread._models import BaseModel BaseModelT = TypeVar("BaseModelT", bound=BaseModel) @@ -27,12 +28,12 @@ def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool: for name, field in get_model_fields(model).items(): field_value = getattr(value, name) - if PYDANTIC_V2: - allow_none = False - else: + if PYDANTIC_V1: # in v1 nullability was structured differently # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields allow_none = getattr(field, "allow_none", False) + else: + allow_none = False assert_matches_type( field_outer_type(field), @@ -71,6 +72,13 @@ def assert_matches_type( if is_list_type(type_): return _assert_list_type(type_, value) + if is_sequence_type(type_): + assert isinstance(value, Sequence) + inner_type = get_args(type_)[0] + for entry in value: # type: ignore + assert_type(inner_type, entry) # type: ignore + return + if origin == str: assert isinstance(value, str) elif origin == int: