Skip to content

Commit 1a3aae7

Browse files
[fern-generated] Update SDK
Generated by Fern CLI Version: unknown Generators: - fernapi/fern-python-sdk: 5.29.3
1 parent 953f5a1 commit 1a3aae7

11 files changed

Lines changed: 280 additions & 226 deletions

.fern/metadata.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"cliVersion": "5.55.0",
33
"generatorName": "fernapi/fern-python-sdk",
4-
"generatorVersion": "5.22.1",
4+
"generatorVersion": "5.29.3",
55
"generatorConfig": {
66
"tcp_keepalive": {
77
"enabled": true,
@@ -100,10 +100,10 @@
100100
}
101101
]
102102
},
103-
"originGitCommit": "5c526877987b8b4e11151461d0c251002eb24341",
103+
"originGitCommit": "bbc2dda3546069d5bed7806a17ca9e2fdfb47df1",
104104
"originGitCommitIsDirty": false,
105105
"invokedBy": "ci",
106-
"requestedVersion": "7.1.0",
106+
"requestedVersion": "7.1.1",
107107
"ciProvider": "github",
108-
"sdkVersion": "7.1.0"
108+
"sdkVersion": "7.1.1"
109109
}

poetry.lock

Lines changed: 132 additions & 132 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ dynamic = ["version"]
44

55
[tool.poetry]
66
name = "cohere"
7-
version = "7.1.0"
7+
version = "7.1.1"
88
description = ""
99
readme = "README.md"
1010
authors = []

src/cohere/base_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,7 +2010,7 @@ async def chat_stream(
20102010
20112011
20122012
async def main() -> None:
2013-
response = await client.chat_stream(
2013+
response = client.chat_stream(
20142014
model="command-a-03-2025",
20152015
message="hello!",
20162016
)
@@ -2484,7 +2484,7 @@ async def generate_stream(
24842484
24852485
24862486
async def main() -> None:
2487-
response = await client.generate_stream(
2487+
response = client.generate_stream(
24882488
prompt="Please explain to me how LLMs work",
24892489
)
24902490
async for chunk in response:

src/cohere/core/client_wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ def get_headers(self) -> typing.Dict[str, str]:
3535
import platform
3636

3737
headers: typing.Dict[str, str] = {
38-
"User-Agent": "cohere/7.1.0",
38+
"User-Agent": "cohere/7.1.1",
3939
"X-Fern-Language": "Python",
4040
"X-Fern-Runtime": f"python/{platform.python_version()}",
4141
"X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",
4242
"X-Fern-SDK-Name": "cohere",
43-
"X-Fern-SDK-Version": "7.1.0",
43+
"X-Fern-SDK-Version": "7.1.1",
4444
**(self.get_custom_headers() or {}),
4545
}
4646
if self._client_name is not None:

src/cohere/core/http_client.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,16 @@ def get_request_body(
272272
data: typing.Optional[typing.Any],
273273
request_options: typing.Optional[RequestOptions],
274274
omit: typing.Optional[typing.Any],
275+
optional_body: bool = False,
275276
) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]:
277+
# A whole body left at the sentinel was never passed by the caller, so it is absent
278+
# rather than empty: the request carries no content and no `Content-Type`.
279+
if omit is not None:
280+
if json is omit:
281+
json = None
282+
if data is omit:
283+
data = None
284+
276285
json_body = None
277286
data_body = None
278287
if data is not None:
@@ -288,14 +297,36 @@ def get_request_body(
288297
# Only collapse empty dict to None when the body was not explicitly provided
289298
# and there are no additional body parameters. This preserves explicit empty
290299
# bodies (e.g., when an endpoint has a request body type but all fields are optional).
291-
if json_body == {} and json is None and not has_additional_body_parameters:
300+
# `optional_body` marks an endpoint whose body the API does not require, where a body
301+
# that ends up empty means the caller passed none of its properties, so the request is
302+
# sent with no content and no `Content-Type`.
303+
if json_body == {} and (json is None or optional_body) and not has_additional_body_parameters:
292304
json_body = None
293-
if data_body == {} and data is None and not has_additional_body_parameters:
305+
if data_body == {} and (data is None or optional_body) and not has_additional_body_parameters:
294306
data_body = None
295307

296308
return json_body, data_body
297309

298310

311+
def drop_content_type_without_body(
312+
headers: typing.Dict[str, typing.Any],
313+
*,
314+
json_body: typing.Optional[typing.Any],
315+
data_body: typing.Optional[typing.Any],
316+
optional_body: bool,
317+
) -> typing.Dict[str, typing.Any]:
318+
"""Strip ``Content-Type`` from a request that carries no body.
319+
320+
``get_request_body`` drops the body of an ``optional_body`` endpoint when the caller
321+
supplied none of it, but the endpoint still passes the content type it would have used.
322+
A request that sends nothing must not advertise a media type, so a server that branches
323+
on the header sees a bodyless call for what it is.
324+
"""
325+
if not optional_body or json_body is not None or data_body is not None:
326+
return headers
327+
return {key: value for key, value in headers.items() if key.lower() != "content-type"}
328+
329+
299330
class HttpClient:
300331
def __init__(
301332
self,
@@ -343,6 +374,7 @@ def request(
343374
request_options: typing.Optional[RequestOptions] = None,
344375
retries: int = 0,
345376
omit: typing.Optional[typing.Any] = None,
377+
optional_body: bool = False,
346378
force_multipart: typing.Optional[bool] = None,
347379
) -> httpx.Response:
348380
base_url = self.get_base_url(base_url)
@@ -355,7 +387,9 @@ def request(
355387
)
356388
timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT
357389

358-
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
390+
json_body, data_body = get_request_body(
391+
json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body
392+
)
359393

360394
request_files: typing.Optional[RequestFiles] = (
361395
convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
@@ -398,6 +432,9 @@ def request(
398432
}
399433
)
400434
)
435+
_request_headers = drop_content_type_without_body(
436+
_request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body
437+
)
401438

402439
if self.logger.is_debug():
403440
self.logger.debug(
@@ -506,6 +543,7 @@ def stream(
506543
request_options: typing.Optional[RequestOptions] = None,
507544
retries: int = 0,
508545
omit: typing.Optional[typing.Any] = None,
546+
optional_body: bool = False,
509547
force_multipart: typing.Optional[bool] = None,
510548
) -> typing.Iterator[httpx.Response]:
511549
base_url = self.get_base_url(base_url)
@@ -527,7 +565,9 @@ def stream(
527565
if (request_files is None or len(request_files) == 0) and force_multipart:
528566
request_files = FORCE_MULTIPART
529567

530-
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
568+
json_body, data_body = get_request_body(
569+
json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body
570+
)
531571

532572
data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart)
533573

@@ -561,6 +601,9 @@ def stream(
561601
}
562602
)
563603
)
604+
_request_headers = drop_content_type_without_body(
605+
_request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body
606+
)
564607

565608
if self.logger.is_debug():
566609
self.logger.debug(
@@ -638,6 +681,7 @@ async def request(
638681
request_options: typing.Optional[RequestOptions] = None,
639682
retries: int = 0,
640683
omit: typing.Optional[typing.Any] = None,
684+
optional_body: bool = False,
641685
force_multipart: typing.Optional[bool] = None,
642686
) -> httpx.Response:
643687
base_url = self.get_base_url(base_url)
@@ -659,7 +703,9 @@ async def request(
659703
if (request_files is None or len(request_files) == 0) and force_multipart:
660704
request_files = FORCE_MULTIPART
661705

662-
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
706+
json_body, data_body = get_request_body(
707+
json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body
708+
)
663709

664710
data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart)
665711

@@ -696,6 +742,9 @@ async def request(
696742
}
697743
)
698744
)
745+
_request_headers = drop_content_type_without_body(
746+
_request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body
747+
)
699748

700749
if self.logger.is_debug():
701750
self.logger.debug(
@@ -804,6 +853,7 @@ async def stream(
804853
request_options: typing.Optional[RequestOptions] = None,
805854
retries: int = 0,
806855
omit: typing.Optional[typing.Any] = None,
856+
optional_body: bool = False,
807857
force_multipart: typing.Optional[bool] = None,
808858
) -> typing.AsyncIterator[httpx.Response]:
809859
base_url = self.get_base_url(base_url)
@@ -825,7 +875,9 @@ async def stream(
825875
if (request_files is None or len(request_files) == 0) and force_multipart:
826876
request_files = FORCE_MULTIPART
827877

828-
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
878+
json_body, data_body = get_request_body(
879+
json=json, data=data, request_options=request_options, omit=omit, optional_body=optional_body
880+
)
829881

830882
data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart)
831883

@@ -862,6 +914,9 @@ async def stream(
862914
}
863915
)
864916
)
917+
_request_headers = drop_content_type_without_body(
918+
_request_headers, json_body=json_body, data_body=data_body, optional_body=optional_body
919+
)
865920

866921
if self.logger.is_debug():
867922
self.logger.debug(

src/cohere/core/http_response.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ def headers(self) -> Dict[str, str]:
2424
def status_code(self) -> int:
2525
return self._response.status_code
2626

27+
@property
28+
def response(self) -> httpx.Response:
29+
return self._response
30+
2731

2832
class HttpResponse(Generic[T], BaseHttpResponse):
2933
"""HTTP response wrapper that exposes response headers and data."""

src/cohere/core/jsonable_encoder.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from pathlib import PurePath
1616
from types import GeneratorType
1717
from typing import Any, Callable, Dict, List, Optional, Set, Union
18+
from urllib.parse import quote
1819

1920
import pydantic
2021
from .datetime_utils import serialize_datetime
@@ -118,3 +119,15 @@ def encode_path_param(obj: Any) -> str:
118119
if isinstance(obj, bool):
119120
return "true" if obj else "false"
120121
return str(jsonable_encoder(obj))
122+
123+
124+
def quote_path_param(obj: Any) -> str:
125+
"""Encode a value for use in a URL path segment, percent-encoding it.
126+
127+
Same as encode_path_param, except the result is percent-encoded so
128+
that a value containing "/" or ".." cannot change which endpoint
129+
the request resolves to.
130+
"""
131+
if isinstance(obj, bool):
132+
return "true" if obj else "false"
133+
return quote(str(jsonable_encoder(obj)), safe="")

0 commit comments

Comments
 (0)