Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.0.1"
".": "0.0.2"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 9
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/indices%2Findices-cd0a3145ae31185097ba45494584157265a7cb10f868a6de68660a70c98a615c.yml
openapi_spec_hash: a96dd8e87af87ca79afa12191e603fc4
config_hash: 3ca969b51ac88362b5119a175c138d83
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/indices%2Findices-c4233dcba86ea16952f3fec0a202133703346d009015e1c91f84061308df0b28.yml
openapi_spec_hash: 85f1a5446d0a15f1aa5af83be0983662
config_hash: 592d5c65c4c2fcf5097bdaaecad615a7
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## 0.0.2 (2025-11-27)

Full Changelog: [v0.0.1...v0.0.2](https://github.com/indicesio/indices-python/compare/v0.0.1...v0.0.2)

### Features

* **api:** config updates ([88eadb3](https://github.com/indicesio/indices-python/commit/88eadb3d17bb0ada31c4fcd6fed1838d47916e81))
* **api:** update openapi spec (v1beta, etc) ([546f449](https://github.com/indicesio/indices-python/commit/546f44918e9b02c6cda3abcab760f2d954e955ec))


### Bug Fixes

* **client:** close streams without requiring full consumption ([959e13b](https://github.com/indicesio/indices-python/commit/959e13b69b92580d77aaa31f33adc3abccb50589))
* compat with Python 3.14 ([a37a195](https://github.com/indicesio/indices-python/commit/a37a1954165cc58e4b2f48cf8fb071455b191900))
* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([b4d1653](https://github.com/indicesio/indices-python/commit/b4d165320434b8447770e0cb417be404d21e0559))


### Chores

* add Python 3.14 classifier and testing ([541071b](https://github.com/indicesio/indices-python/commit/541071bf220a04ad50593691d34f2d3a96848f5f))
* **internal/tests:** avoid race condition with implicit client cleanup ([844f1fb](https://github.com/indicesio/indices-python/commit/844f1fb36c363c4e60d05bd311ed96115db3244c))
* **internal:** grammar fix (it's -> its) ([49e8427](https://github.com/indicesio/indices-python/commit/49e8427b5299817d6bc5a06cec3deed1f164cbc0))
* **package:** drop Python 3.8 support ([daf5eee](https://github.com/indicesio/indices-python/commit/daf5eee6e9478719c680e775530b436b90870a84))

## 0.0.1 (2025-10-20)

Full Changelog: [v0.0.1...v0.0.1](https://github.com/indicesio/indices-python/compare/v0.0.1...v0.0.1)
Expand Down
43 changes: 31 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- prettier-ignore -->
[![PyPI version](https://img.shields.io/pypi/v/indices.svg?label=pypi%20(stable))](https://pypi.org/project/indices/)

The Indices Python library provides convenient access to the Indices REST API from any Python 3.8+
The Indices Python library provides convenient access to the Indices 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).

Expand Down Expand Up @@ -32,7 +32,10 @@ client = Indices(
api_key=os.environ.get("INDICES_API_KEY"), # This is the default and can be omitted
)

tasks = client.tasks.list()
run = client.runs.run(
task_id="<your_task_id>",
)
print(run.id)
```

While you can provide an `api_key` keyword argument,
Expand All @@ -55,7 +58,10 @@ client = AsyncIndices(


async def main() -> None:
tasks = await client.tasks.list()
run = await client.runs.run(
task_id="<your_task_id>",
)
print(run.id)


asyncio.run(main())
Expand Down Expand Up @@ -87,7 +93,10 @@ async def main() -> None:
api_key="My API Key",
http_client=DefaultAioHttpClient(),
) as client:
tasks = await client.tasks.list()
run = await client.runs.run(
task_id="<your_task_id>",
)
print(run.id)


asyncio.run(main())
Expand Down Expand Up @@ -118,7 +127,9 @@ from indices import Indices
client = Indices()

try:
client.tasks.list()
client.runs.run(
task_id="<your_task_id>",
)
except indices.APIConnectionError as e:
print("The server could not be reached")
print(e.__cause__) # an underlying Exception, likely raised within httpx.
Expand Down Expand Up @@ -161,7 +172,9 @@ client = Indices(
)

# Or, configure per-request:
client.with_options(max_retries=5).tasks.list()
client.with_options(max_retries=5).runs.run(
task_id="<your_task_id>",
)
```

### Timeouts
Expand All @@ -184,7 +197,9 @@ client = Indices(
)

# Override per-request:
client.with_options(timeout=5.0).tasks.list()
client.with_options(timeout=5.0).runs.run(
task_id="<your_task_id>",
)
```

On timeout, an `APITimeoutError` is thrown.
Expand Down Expand Up @@ -225,11 +240,13 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to
from indices import Indices

client = Indices()
response = client.tasks.with_raw_response.list()
response = client.runs.with_raw_response.run(
task_id="<your_task_id>",
)
print(response.headers.get('X-My-Header'))

task = response.parse() # get the object that `tasks.list()` would have returned
print(task)
run = response.parse() # get the object that `runs.run()` would have returned
print(run.id)
```

These methods return an [`APIResponse`](https://github.com/indicesio/indices-python/tree/main/src/indices/_response.py) object.
Expand All @@ -243,7 +260,9 @@ 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.tasks.with_streaming_response.list() as response:
with client.runs.with_streaming_response.run(
task_id="<your_task_id>",
) as response:
print(response.headers.get("X-My-Header"))

for line in response.iter_lines():
Expand Down Expand Up @@ -353,7 +372,7 @@ print(indices.__version__)

## Requirements

Python 3.8 or higher.
Python 3.9 or higher.

## Contributing

Expand Down
18 changes: 9 additions & 9 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ from indices.types import Task, TaskListResponse, TaskStartManualSessionResponse

Methods:

- <code title="post /v1/tasks">client.tasks.<a href="./src/indices/resources/tasks.py">create</a>(\*\*<a href="src/indices/types/task_create_params.py">params</a>) -> <a href="./src/indices/types/task.py">Task</a></code>
- <code title="get /v1/tasks/{id}">client.tasks.<a href="./src/indices/resources/tasks.py">retrieve</a>(id) -> <a href="./src/indices/types/task.py">Task</a></code>
- <code title="get /v1/tasks">client.tasks.<a href="./src/indices/resources/tasks.py">list</a>() -> <a href="./src/indices/types/task_list_response.py">TaskListResponse</a></code>
- <code title="delete /v1/tasks/{id}">client.tasks.<a href="./src/indices/resources/tasks.py">delete</a>(id) -> object</code>
- <code title="post /v1/tasks/{id}/complete-manual-session">client.tasks.<a href="./src/indices/resources/tasks.py">complete_manual_session</a>(id) -> object</code>
- <code title="post /v1/tasks/{id}/start-manual-session">client.tasks.<a href="./src/indices/resources/tasks.py">start_manual_session</a>(id, \*\*<a href="src/indices/types/task_start_manual_session_params.py">params</a>) -> <a href="./src/indices/types/task_start_manual_session_response.py">TaskStartManualSessionResponse</a></code>
- <code title="post /v1beta/tasks">client.tasks.<a href="./src/indices/resources/tasks.py">create</a>(\*\*<a href="src/indices/types/task_create_params.py">params</a>) -> <a href="./src/indices/types/task.py">Task</a></code>
- <code title="get /v1beta/tasks/{id}">client.tasks.<a href="./src/indices/resources/tasks.py">retrieve</a>(id) -> <a href="./src/indices/types/task.py">Task</a></code>
- <code title="get /v1beta/tasks">client.tasks.<a href="./src/indices/resources/tasks.py">list</a>() -> <a href="./src/indices/types/task_list_response.py">TaskListResponse</a></code>
- <code title="delete /v1beta/tasks/{id}">client.tasks.<a href="./src/indices/resources/tasks.py">delete</a>(id) -> object</code>
- <code title="post /v1beta/tasks/{id}/complete-manual-session">client.tasks.<a href="./src/indices/resources/tasks.py">complete_manual_session</a>(id) -> object</code>
- <code title="post /v1beta/tasks/{id}/start-manual-session">client.tasks.<a href="./src/indices/resources/tasks.py">start_manual_session</a>(id, \*\*<a href="src/indices/types/task_start_manual_session_params.py">params</a>) -> <a href="./src/indices/types/task_start_manual_session_response.py">TaskStartManualSessionResponse</a></code>

# Runs

Expand All @@ -25,6 +25,6 @@ from indices.types import Run, RunListResponse

Methods:

- <code title="get /v1/runs/{run_id}">client.runs.<a href="./src/indices/resources/runs.py">retrieve</a>(run_id) -> <a href="./src/indices/types/run.py">Run</a></code>
- <code title="get /v1/runs">client.runs.<a href="./src/indices/resources/runs.py">list</a>(\*\*<a href="src/indices/types/run_list_params.py">params</a>) -> <a href="./src/indices/types/run_list_response.py">RunListResponse</a></code>
- <code title="post /v1/runs">client.runs.<a href="./src/indices/resources/runs.py">run</a>(\*\*<a href="src/indices/types/run_run_params.py">params</a>) -> <a href="./src/indices/types/run.py">Run</a></code>
- <code title="get /v1beta/runs/{run_id}">client.runs.<a href="./src/indices/resources/runs.py">retrieve</a>(run_id) -> <a href="./src/indices/types/run.py">Run</a></code>
- <code title="get /v1beta/runs">client.runs.<a href="./src/indices/resources/runs.py">list</a>(\*\*<a href="src/indices/types/run_list_params.py">params</a>) -> <a href="./src/indices/types/run_list_response.py">RunListResponse</a></code>
- <code title="post /v1beta/runs">client.runs.<a href="./src/indices/resources/runs.py">run</a>(\*\*<a href="src/indices/types/run_run_params.py">params</a>) -> <a href="./src/indices/types/run.py">Run</a></code>
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "indices"
version = "0.0.1"
version = "0.0.2"
description = "The official Python library for the indices API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand All @@ -15,16 +15,16 @@ dependencies = [
"distro>=1.7.0, <2",
"sniffio",
]
requires-python = ">= 3.8"
requires-python = ">= 3.9"
classifiers = [
"Typing :: Typed",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"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",
Expand Down Expand Up @@ -141,7 +141,7 @@ filterwarnings = [
# there are a couple of flags that are still disabled by
# default in strict mode as they are experimental and niche.
typeCheckingMode = "strict"
pythonVersion = "3.8"
pythonVersion = "3.9"

exclude = [
"_dev",
Expand Down
52 changes: 37 additions & 15 deletions src/indices/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os
import inspect
import weakref
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
from datetime import date, datetime
from typing_extensions import (
Expand Down Expand Up @@ -256,32 +257,41 @@ def model_dump(
mode: Literal["json", "python"] | str = "python",
include: IncEx | None = None,
exclude: IncEx | None = None,
context: Any | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
context: dict[str, Any] | None = None,
serialize_as_any: bool = False,
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
) -> dict[str, Any]:
"""Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump

Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

Args:
mode: The mode in which `to_python` should run.
If mode is 'json', the dictionary will only contain JSON serializable types.
If mode is 'python', the dictionary may contain any Python objects.
include: A list of fields to include in the output.
exclude: A list of fields to exclude from the output.
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.
Expand All @@ -298,6 +308,8 @@ def model_dump(
raise ValueError("serialize_as_any is only supported in Pydantic v2")
if fallback is not None:
raise ValueError("fallback is only supported in Pydantic v2")
if exclude_computed_fields != False:
raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
dumped = super().dict( # pyright: ignore[reportDeprecated]
include=include,
exclude=exclude,
Expand All @@ -314,15 +326,17 @@ def model_dump_json(
self,
*,
indent: int | None = None,
ensure_ascii: bool = False,
include: IncEx | None = None,
exclude: IncEx | None = None,
context: Any | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
context: dict[str, Any] | None = None,
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
) -> str:
Expand Down Expand Up @@ -354,6 +368,10 @@ def model_dump_json(
raise ValueError("serialize_as_any is only supported in Pydantic v2")
if fallback is not None:
raise ValueError("fallback is only supported in Pydantic v2")
if ensure_ascii != False:
raise ValueError("ensure_ascii is only supported in Pydantic v2")
if exclude_computed_fields != False:
raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
return super().json( # type: ignore[reportDeprecated]
indent=indent,
include=include,
Expand Down Expand Up @@ -573,6 +591,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.
Expand Down Expand Up @@ -615,8 +636,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

Expand Down Expand Up @@ -669,7 +691,7 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
discriminator_field=discriminator_field_name,
discriminator_alias=discriminator_alias,
)
cast(CachedDiscriminatorType, union).__discriminator__ = details
DISCRIMINATOR_CACHE.setdefault(union, details)
return details


Expand Down
10 changes: 4 additions & 6 deletions src/indices/_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ def __stream__(self) -> Iterator[_T]:
for sse in iterator:
yield process_data(data=sse.json(), cast_to=cast_to, response=response)

# Ensure the entire stream is consumed
for _sse in iterator:
...
# As we might not fully consume the response stream, we need to close it explicitly
response.close()

def __enter__(self) -> Self:
return self
Expand Down Expand Up @@ -121,9 +120,8 @@ async def __stream__(self) -> AsyncIterator[_T]:
async for sse in iterator:
yield process_data(data=sse.json(), cast_to=cast_to, response=response)

# Ensure the entire stream is consumed
async for _sse in iterator:
...
# As we might not fully consume the response stream, we need to close it explicitly
await response.aclose()

async def __aenter__(self) -> Self:
return self
Expand Down
Loading