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
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ jobs:
lint:
name: lint
runs-on: ubuntu-latest


steps:
- uses: actions/checkout@v4
Expand All @@ -30,6 +29,7 @@ jobs:

- name: Run lints
run: ./scripts/lint

test:
name: test
runs-on: ubuntu-latest
Expand All @@ -50,4 +50,3 @@ jobs:

- name: Run tests
run: ./scripts/test

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.1.0"
".": "0.2.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
configured_endpoints: 33
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/contextual-ai%2Fsunrise-4ed32c3243ce7a772e55bb1ba204736fc3fb1d712d8ca0eb91bac0c7ac626938.yml
configured_endpoints: 35
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/contextual-ai%2Fsunrise-d79ccb778953ad5c2ae4b99115429c8b3f68b3b23d9b6d90b1b40393f11a4383.yml
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
# Changelog

## 0.2.0 (2025-02-08)

Full Changelog: [v0.1.0...v0.2.0](https://github.com/ContextualAI/contextual-client-python/compare/v0.1.0...v0.2.0)

### Features

* **api:** update via SDK Studio ([#31](https://github.com/ContextualAI/contextual-client-python/issues/31)) ([c9de385](https://github.com/ContextualAI/contextual-client-python/commit/c9de38561c8663d1e00daa381fcb3183501993cf))
* **api:** update via SDK Studio ([#32](https://github.com/ContextualAI/contextual-client-python/issues/32)) ([c166d77](https://github.com/ContextualAI/contextual-client-python/commit/c166d77d241e104a80ce0cddeaf2b5cfe7c59669))
* **api:** update via SDK Studio ([#39](https://github.com/ContextualAI/contextual-client-python/issues/39)) ([9f8c0a6](https://github.com/ContextualAI/contextual-client-python/commit/9f8c0a6d4203953f195cfe5d38a69f8870bc0a9e))
* **client:** send `X-Stainless-Read-Timeout` header ([#35](https://github.com/ContextualAI/contextual-client-python/issues/35)) ([2ddba9d](https://github.com/ContextualAI/contextual-client-python/commit/2ddba9dc9d8cb0b562c6dd7f8a3a21e2c82295bc))


### Bug Fixes

* **tests:** make test_get_platform less flaky ([#26](https://github.com/ContextualAI/contextual-client-python/issues/26)) ([3bc8a69](https://github.com/ContextualAI/contextual-client-python/commit/3bc8a69c6e9255dc1e3247fd1954e5deb5e1c155))


### Chores

* **internal:** avoid pytest-asyncio deprecation warning ([#27](https://github.com/ContextualAI/contextual-client-python/issues/27)) ([e6f70cd](https://github.com/ContextualAI/contextual-client-python/commit/e6f70cdff84defcb3b9d77e3aa0c66e9d17774d5))
* **internal:** bummp ruff dependency ([#34](https://github.com/ContextualAI/contextual-client-python/issues/34)) ([f3a23c2](https://github.com/ContextualAI/contextual-client-python/commit/f3a23c21168a5ef99626e50782ae902c780b4059))
* **internal:** change default timeout to an int ([#33](https://github.com/ContextualAI/contextual-client-python/issues/33)) ([280fc1f](https://github.com/ContextualAI/contextual-client-python/commit/280fc1fcce2a011bda2b895b39b85db682cc0c8c))
* **internal:** codegen related update ([#23](https://github.com/ContextualAI/contextual-client-python/issues/23)) ([d1f86c3](https://github.com/ContextualAI/contextual-client-python/commit/d1f86c3bc54440925725dd9c535082fa7d29d100))
* **internal:** codegen related update ([#30](https://github.com/ContextualAI/contextual-client-python/issues/30)) ([0cbc82e](https://github.com/ContextualAI/contextual-client-python/commit/0cbc82e361567e9f0c44f9b5519d404fcba91fef))
* **internal:** fix type traversing dictionary params ([#36](https://github.com/ContextualAI/contextual-client-python/issues/36)) ([04a1eab](https://github.com/ContextualAI/contextual-client-python/commit/04a1eaba9f246089baa2c26dac29b22e9f63f9dc))
* **internal:** minor formatting changes ([#29](https://github.com/ContextualAI/contextual-client-python/issues/29)) ([9d063fb](https://github.com/ContextualAI/contextual-client-python/commit/9d063fbf86e64803fcc684305a67dae3a31775a0))
* **internal:** minor style changes ([#28](https://github.com/ContextualAI/contextual-client-python/issues/28)) ([1cbda0a](https://github.com/ContextualAI/contextual-client-python/commit/1cbda0a834e06cbb4afdbc922e4e9f894cb21d40))
* **internal:** minor type handling changes ([#37](https://github.com/ContextualAI/contextual-client-python/issues/37)) ([dd9a8e8](https://github.com/ContextualAI/contextual-client-python/commit/dd9a8e898c56fc55b9e61de09419a66ad398b7b3))


### Documentation

* **raw responses:** fix duplicate `the` ([#25](https://github.com/ContextualAI/contextual-client-python/issues/25)) ([5342fdf](https://github.com/ContextualAI/contextual-client-python/commit/5342fdfbecdd99f14d0033736ebf91700bc74f0e))

## 0.1.0 (2025-01-15)

Full Changelog: [v0.1.0-alpha.2...v0.1.0](https://github.com/ContextualAI/contextual-client-python/compare/v0.1.0-alpha.2...v0.1.0)
Expand Down
24 changes: 24 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,27 @@ from contextual.types import LMUnitCreateResponse
Methods:

- <code title="post /lmunit">client.lmunit.<a href="./src/contextual/resources/lmunit.py">create</a>(\*\*<a href="src/contextual/types/lmunit_create_params.py">params</a>) -> <a href="./src/contextual/types/lmunit_create_response.py">LMUnitCreateResponse</a></code>

# Rerank

Types:

```python
from contextual.types import RerankCreateResponse
```

Methods:

- <code title="post /rerank">client.rerank.<a href="./src/contextual/resources/rerank.py">create</a>(\*\*<a href="src/contextual/types/rerank_create_params.py">params</a>) -> <a href="./src/contextual/types/rerank_create_response.py">RerankCreateResponse</a></code>

# Generate

Types:

```python
from contextual.types import GenerateCreateResponse
```

Methods:

- <code title="post /generate">client.generate.<a href="./src/contextual/resources/generate.py">create</a>(\*\*<a href="src/contextual/types/generate_create_params.py">params</a>) -> <a href="./src/contextual/types/generate_create_response.py">GenerateCreateResponse</a></code>
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ cache_fine_grained = True
# ```
# Changing this codegen to make mypy happy would increase complexity
# and would not be worth it.
disable_error_code = func-returns-value
disable_error_code = func-returns-value,overload-cannot-match

# https://github.com/python/mypy/issues/12162
[mypy.overrides]
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "contextual-client"
version = "0.1.0"
version = "0.2.0"
description = "The official Python library for the Contextual AI API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down Expand Up @@ -129,6 +129,7 @@ testpaths = ["tests"]
addopts = "--tb=short"
xfail_strict = true
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
filterwarnings = [
"error"
]
Expand Down Expand Up @@ -176,7 +177,7 @@ select = [
"T201",
"T203",
# misuse of typing.TYPE_CHECKING
"TCH004",
"TC004",
# import rules
"TID251",
]
Expand Down
6 changes: 3 additions & 3 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ markdown-it-py==3.0.0
# via rich
mdurl==0.1.2
# via markdown-it-py
mypy==1.13.0
mypy==1.14.1
mypy-extensions==1.0.0
# via mypy
nest-asyncio==1.6.0
Expand All @@ -69,7 +69,7 @@ pydantic-core==2.27.1
# via pydantic
pygments==2.18.0
# via rich
pyright==1.1.390
pyright==1.1.392.post0
pytest==8.3.3
# via pytest-asyncio
pytest-asyncio==0.24.0
Expand All @@ -79,7 +79,7 @@ pytz==2023.3.post1
# via dirty-equals
respx==0.22.0
rich==13.7.1
ruff==0.6.9
ruff==0.9.4
setuptools==68.2.2
# via nodeenv
six==1.16.0
Expand Down
2 changes: 1 addition & 1 deletion scripts/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -e

cd "$(dirname "$0")/.."

if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
brew bundle check >/dev/null 2>&1 || {
echo "==> Installing Homebrew dependencies…"
brew bundle
Expand Down
1 change: 0 additions & 1 deletion scripts/lint
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ rye run lint

echo "==> Making sure it imports"
rye run python -c 'import contextual'

4 changes: 2 additions & 2 deletions scripts/utils/ruffen-docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions src/contextual/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,17 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
headers[idempotency_header] = options.idempotency_key or self._idempotency_key()

# Don't set the retry count header if it was already set or removed by the caller. We check
# Don't set these headers if they were already set or removed by the caller. We check
# `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
if "x-stainless-retry-count" not in (header.lower() for header in custom_headers):
lower_custom_headers = [header.lower() for header in custom_headers]
if "x-stainless-retry-count" not in lower_custom_headers:
headers["x-stainless-retry-count"] = str(retries_taken)
if "x-stainless-read-timeout" not in lower_custom_headers:
timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
if isinstance(timeout, Timeout):
timeout = timeout.read
if timeout is not None:
headers["x-stainless-read-timeout"] = str(timeout)

return headers

Expand Down
18 changes: 17 additions & 1 deletion src/contextual/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
get_async_library,
)
from ._version import __version__
from .resources import lmunit
from .resources import lmunit, rerank, generate
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import APIStatusError, ContextualAIError
from ._base_client import (
Expand All @@ -51,6 +51,8 @@ class ContextualAI(SyncAPIClient):
datastores: datastores.DatastoresResource
agents: agents.AgentsResource
lmunit: lmunit.LMUnitResource
rerank: rerank.RerankResource
generate: generate.GenerateResource
with_raw_response: ContextualAIWithRawResponse
with_streaming_response: ContextualAIWithStreamedResponse

Expand Down Expand Up @@ -117,6 +119,8 @@ def __init__(
self.datastores = datastores.DatastoresResource(self)
self.agents = agents.AgentsResource(self)
self.lmunit = lmunit.LMUnitResource(self)
self.rerank = rerank.RerankResource(self)
self.generate = generate.GenerateResource(self)
self.with_raw_response = ContextualAIWithRawResponse(self)
self.with_streaming_response = ContextualAIWithStreamedResponse(self)

Expand Down Expand Up @@ -232,6 +236,8 @@ class AsyncContextualAI(AsyncAPIClient):
datastores: datastores.AsyncDatastoresResource
agents: agents.AsyncAgentsResource
lmunit: lmunit.AsyncLMUnitResource
rerank: rerank.AsyncRerankResource
generate: generate.AsyncGenerateResource
with_raw_response: AsyncContextualAIWithRawResponse
with_streaming_response: AsyncContextualAIWithStreamedResponse

Expand Down Expand Up @@ -298,6 +304,8 @@ def __init__(
self.datastores = datastores.AsyncDatastoresResource(self)
self.agents = agents.AsyncAgentsResource(self)
self.lmunit = lmunit.AsyncLMUnitResource(self)
self.rerank = rerank.AsyncRerankResource(self)
self.generate = generate.AsyncGenerateResource(self)
self.with_raw_response = AsyncContextualAIWithRawResponse(self)
self.with_streaming_response = AsyncContextualAIWithStreamedResponse(self)

Expand Down Expand Up @@ -414,27 +422,35 @@ def __init__(self, client: ContextualAI) -> None:
self.datastores = datastores.DatastoresResourceWithRawResponse(client.datastores)
self.agents = agents.AgentsResourceWithRawResponse(client.agents)
self.lmunit = lmunit.LMUnitResourceWithRawResponse(client.lmunit)
self.rerank = rerank.RerankResourceWithRawResponse(client.rerank)
self.generate = generate.GenerateResourceWithRawResponse(client.generate)


class AsyncContextualAIWithRawResponse:
def __init__(self, client: AsyncContextualAI) -> None:
self.datastores = datastores.AsyncDatastoresResourceWithRawResponse(client.datastores)
self.agents = agents.AsyncAgentsResourceWithRawResponse(client.agents)
self.lmunit = lmunit.AsyncLMUnitResourceWithRawResponse(client.lmunit)
self.rerank = rerank.AsyncRerankResourceWithRawResponse(client.rerank)
self.generate = generate.AsyncGenerateResourceWithRawResponse(client.generate)


class ContextualAIWithStreamedResponse:
def __init__(self, client: ContextualAI) -> None:
self.datastores = datastores.DatastoresResourceWithStreamingResponse(client.datastores)
self.agents = agents.AgentsResourceWithStreamingResponse(client.agents)
self.lmunit = lmunit.LMUnitResourceWithStreamingResponse(client.lmunit)
self.rerank = rerank.RerankResourceWithStreamingResponse(client.rerank)
self.generate = generate.GenerateResourceWithStreamingResponse(client.generate)


class AsyncContextualAIWithStreamedResponse:
def __init__(self, client: AsyncContextualAI) -> None:
self.datastores = datastores.AsyncDatastoresResourceWithStreamingResponse(client.datastores)
self.agents = agents.AsyncAgentsResourceWithStreamingResponse(client.agents)
self.lmunit = lmunit.AsyncLMUnitResourceWithStreamingResponse(client.lmunit)
self.rerank = rerank.AsyncRerankResourceWithStreamingResponse(client.rerank)
self.generate = generate.AsyncGenerateResourceWithStreamingResponse(client.generate)


Client = ContextualAI
Expand Down
2 changes: 1 addition & 1 deletion src/contextual/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 8 additions & 2 deletions src/contextual/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def to_json(
@override
def __str__(self) -> str:
# mypy complains about an invalid self arg
return f'{self.__repr_name__()}({self.__repr_str__(", ")})' # type: ignore[misc]
return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc]

# Override the 'construct' method in a way that supports recursive parsing without validation.
# Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
Expand Down Expand Up @@ -426,10 +426,16 @@ def construct_type(*, value: object, type_: object) -> object:

If the given value does not match the expected type then it is returned as-is.
"""

# store a reference to the original type we were given before we extract any inner
# types so that we can properly resolve forward references in `TypeAliasType` annotations
original_type = None

# we allow `object` as the input type because otherwise, passing things like
# `Literal['value']` will be reported as a type error by type checkers
type_ = cast("type[object]", type_)
if is_type_alias_type(type_):
original_type = type_ # type: ignore[unreachable]
type_ = type_.__value__ # type: ignore[unreachable]

# unwrap `Annotated[T, ...]` -> `T`
Expand All @@ -446,7 +452,7 @@ def construct_type(*, value: object, type_: object) -> object:

if is_union(origin):
try:
return validate_type(type_=cast("type[object]", type_), value=value)
return validate_type(type_=cast("type[object]", original_type or type_), value=value)
except Exception:
pass

Expand Down
12 changes: 9 additions & 3 deletions src/contextual/_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -195,8 +197,6 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
if cast_to == bool:
return cast(R, response.text.lower() == "true")

origin = get_origin(cast_to) or cast_to

if origin == APIResponse:
raise RuntimeError("Unexpected state - cast_to is `APIResponse`")

Expand All @@ -210,7 +210,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
return cast(R, response)

if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel):
if (
inspect.isclass(
origin # pyright: ignore[reportUnknownArgumentType]
)
and not issubclass(origin, BaseModel)
and issubclass(origin, pydantic.BaseModel)
):
raise TypeError(
"Pydantic models must subclass our base model type, e.g. `from contextual import BaseModel`"
)
Expand Down
Loading