Skip to content

Commit 646ad67

Browse files
stainless-app[bot]stainless-bot
authored andcommitted
chore(internal): codegen related update (#112)
1 parent a1b43d0 commit 646ad67

File tree

6 files changed

+175
-58
lines changed

6 files changed

+175
-58
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ $ pip install -r requirements-dev.lock
3131

3232
## Modifying/Adding code
3333

34-
Most of the SDK is generated code, and any modified code will be overridden on the next generation. The
35-
`src/onebusaway/lib/` and `examples/` directories are exceptions and will never be overridden.
34+
Most of the SDK is generated code. Modifications to code will be persisted between generations, but may
35+
result in merge conflicts between manual patches and changes from the generator. The generator will never
36+
modify the contents of the `src/onebusaway/lib/` and `examples/` directories.
3637

3738
## Adding and running examples
3839

39-
All files in the `examples/` directory are not modified by the Stainless generator and can be freely edited or
40-
added to.
40+
All files in the `examples/` directory are not modified by the generator and can be freely edited or added to.
4141

4242
```bash
4343
# add an example to examples/<your-example>.py

requirements-dev.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ markdown-it-py==3.0.0
5050
# via rich
5151
mdurl==0.1.2
5252
# via markdown-it-py
53-
mypy==1.10.1
53+
mypy==1.11.2
5454
mypy-extensions==1.0.0
5555
# via mypy
5656
nodeenv==1.8.0
@@ -71,7 +71,7 @@ pydantic-core==2.18.2
7171
# via pydantic
7272
pygments==2.18.0
7373
# via rich
74-
pyright==1.1.374
74+
pyright==1.1.380
7575
pytest==7.1.1
7676
# via pytest-asyncio
7777
pytest-asyncio==0.21.1
@@ -81,7 +81,7 @@ pytz==2023.3.post1
8181
# via dirty-equals
8282
respx==0.20.2
8383
rich==13.7.1
84-
ruff==0.5.6
84+
ruff==0.6.5
8585
setuptools==68.2.2
8686
# via nodeenv
8787
six==1.16.0

src/onebusaway/_base_client.py

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -400,14 +400,7 @@ def _make_status_error(
400400
) -> _exceptions.APIStatusError:
401401
raise NotImplementedError()
402402

403-
def _remaining_retries(
404-
self,
405-
remaining_retries: Optional[int],
406-
options: FinalRequestOptions,
407-
) -> int:
408-
return remaining_retries if remaining_retries is not None else options.get_max_retries(self.max_retries)
409-
410-
def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers:
403+
def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers:
411404
custom_headers = options.headers or {}
412405
headers_dict = _merge_mappings(self.default_headers, custom_headers)
413406
self._validate_headers(headers_dict, custom_headers)
@@ -419,6 +412,11 @@ def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers:
419412
if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
420413
headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
421414

415+
# Don't set the retry count header if it was already set or removed by the caller. We check
416+
# `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
417+
if "x-stainless-retry-count" not in (header.lower() for header in custom_headers):
418+
headers["x-stainless-retry-count"] = str(retries_taken)
419+
422420
return headers
423421

424422
def _prepare_url(self, url: str) -> URL:
@@ -440,6 +438,8 @@ def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
440438
def _build_request(
441439
self,
442440
options: FinalRequestOptions,
441+
*,
442+
retries_taken: int = 0,
443443
) -> httpx.Request:
444444
if log.isEnabledFor(logging.DEBUG):
445445
log.debug("Request options: %s", model_dump(options, exclude_unset=True))
@@ -455,7 +455,7 @@ def _build_request(
455455
else:
456456
raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")
457457

458-
headers = self._build_headers(options)
458+
headers = self._build_headers(options, retries_taken=retries_taken)
459459
params = _merge_mappings(self.default_query, options.params)
460460
content_type = headers.get("Content-Type")
461461
files = options.files
@@ -489,12 +489,17 @@ def _build_request(
489489
if not files:
490490
files = cast(HttpxRequestFiles, ForceMultipartDict())
491491

492+
prepared_url = self._prepare_url(options.url)
493+
if "_" in prepared_url.host:
494+
# work around https://github.com/encode/httpx/discussions/2880
495+
kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
496+
492497
# TODO: report this error to httpx
493498
return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
494499
headers=headers,
495500
timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout,
496501
method=options.method,
497-
url=self._prepare_url(options.url),
502+
url=prepared_url,
498503
# the `Query` type that we use is incompatible with qs'
499504
# `Params` type as it needs to be typed as `Mapping[str, object]`
500505
# so that passing a `TypedDict` doesn't cause an error.
@@ -933,20 +938,25 @@ def request(
933938
stream: bool = False,
934939
stream_cls: type[_StreamT] | None = None,
935940
) -> ResponseT | _StreamT:
941+
if remaining_retries is not None:
942+
retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
943+
else:
944+
retries_taken = 0
945+
936946
return self._request(
937947
cast_to=cast_to,
938948
options=options,
939949
stream=stream,
940950
stream_cls=stream_cls,
941-
remaining_retries=remaining_retries,
951+
retries_taken=retries_taken,
942952
)
943953

944954
def _request(
945955
self,
946956
*,
947957
cast_to: Type[ResponseT],
948958
options: FinalRequestOptions,
949-
remaining_retries: int | None,
959+
retries_taken: int,
950960
stream: bool,
951961
stream_cls: type[_StreamT] | None,
952962
) -> ResponseT | _StreamT:
@@ -958,8 +968,8 @@ def _request(
958968
cast_to = self._maybe_override_cast_to(cast_to, options)
959969
options = self._prepare_options(options)
960970

961-
retries = self._remaining_retries(remaining_retries, options)
962-
request = self._build_request(options)
971+
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
972+
request = self._build_request(options, retries_taken=retries_taken)
963973
self._prepare_request(request)
964974

965975
kwargs: HttpxSendArgs = {}
@@ -977,11 +987,11 @@ def _request(
977987
except httpx.TimeoutException as err:
978988
log.debug("Encountered httpx.TimeoutException", exc_info=True)
979989

980-
if retries > 0:
990+
if remaining_retries > 0:
981991
return self._retry_request(
982992
input_options,
983993
cast_to,
984-
retries,
994+
retries_taken=retries_taken,
985995
stream=stream,
986996
stream_cls=stream_cls,
987997
response_headers=None,
@@ -992,11 +1002,11 @@ def _request(
9921002
except Exception as err:
9931003
log.debug("Encountered Exception", exc_info=True)
9941004

995-
if retries > 0:
1005+
if remaining_retries > 0:
9961006
return self._retry_request(
9971007
input_options,
9981008
cast_to,
999-
retries,
1009+
retries_taken=retries_taken,
10001010
stream=stream,
10011011
stream_cls=stream_cls,
10021012
response_headers=None,
@@ -1019,13 +1029,13 @@ def _request(
10191029
except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
10201030
log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
10211031

1022-
if retries > 0 and self._should_retry(err.response):
1032+
if remaining_retries > 0 and self._should_retry(err.response):
10231033
err.response.close()
10241034
return self._retry_request(
10251035
input_options,
10261036
cast_to,
1027-
retries,
1028-
err.response.headers,
1037+
retries_taken=retries_taken,
1038+
response_headers=err.response.headers,
10291039
stream=stream,
10301040
stream_cls=stream_cls,
10311041
)
@@ -1044,26 +1054,26 @@ def _request(
10441054
response=response,
10451055
stream=stream,
10461056
stream_cls=stream_cls,
1047-
retries_taken=options.get_max_retries(self.max_retries) - retries,
1057+
retries_taken=retries_taken,
10481058
)
10491059

10501060
def _retry_request(
10511061
self,
10521062
options: FinalRequestOptions,
10531063
cast_to: Type[ResponseT],
1054-
remaining_retries: int,
1055-
response_headers: httpx.Headers | None,
10561064
*,
1065+
retries_taken: int,
1066+
response_headers: httpx.Headers | None,
10571067
stream: bool,
10581068
stream_cls: type[_StreamT] | None,
10591069
) -> ResponseT | _StreamT:
1060-
remaining = remaining_retries - 1
1061-
if remaining == 1:
1070+
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
1071+
if remaining_retries == 1:
10621072
log.debug("1 retry left")
10631073
else:
1064-
log.debug("%i retries left", remaining)
1074+
log.debug("%i retries left", remaining_retries)
10651075

1066-
timeout = self._calculate_retry_timeout(remaining, options, response_headers)
1076+
timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
10671077
log.info("Retrying request to %s in %f seconds", options.url, timeout)
10681078

10691079
# In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
@@ -1073,7 +1083,7 @@ def _retry_request(
10731083
return self._request(
10741084
options=options,
10751085
cast_to=cast_to,
1076-
remaining_retries=remaining,
1086+
retries_taken=retries_taken + 1,
10771087
stream=stream,
10781088
stream_cls=stream_cls,
10791089
)
@@ -1491,12 +1501,17 @@ async def request(
14911501
stream_cls: type[_AsyncStreamT] | None = None,
14921502
remaining_retries: Optional[int] = None,
14931503
) -> ResponseT | _AsyncStreamT:
1504+
if remaining_retries is not None:
1505+
retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
1506+
else:
1507+
retries_taken = 0
1508+
14941509
return await self._request(
14951510
cast_to=cast_to,
14961511
options=options,
14971512
stream=stream,
14981513
stream_cls=stream_cls,
1499-
remaining_retries=remaining_retries,
1514+
retries_taken=retries_taken,
15001515
)
15011516

15021517
async def _request(
@@ -1506,7 +1521,7 @@ async def _request(
15061521
*,
15071522
stream: bool,
15081523
stream_cls: type[_AsyncStreamT] | None,
1509-
remaining_retries: int | None,
1524+
retries_taken: int,
15101525
) -> ResponseT | _AsyncStreamT:
15111526
if self._platform is None:
15121527
# `get_platform` can make blocking IO calls so we
@@ -1521,8 +1536,8 @@ async def _request(
15211536
cast_to = self._maybe_override_cast_to(cast_to, options)
15221537
options = await self._prepare_options(options)
15231538

1524-
retries = self._remaining_retries(remaining_retries, options)
1525-
request = self._build_request(options)
1539+
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
1540+
request = self._build_request(options, retries_taken=retries_taken)
15261541
await self._prepare_request(request)
15271542

15281543
kwargs: HttpxSendArgs = {}
@@ -1538,11 +1553,11 @@ async def _request(
15381553
except httpx.TimeoutException as err:
15391554
log.debug("Encountered httpx.TimeoutException", exc_info=True)
15401555

1541-
if retries > 0:
1556+
if remaining_retries > 0:
15421557
return await self._retry_request(
15431558
input_options,
15441559
cast_to,
1545-
retries,
1560+
retries_taken=retries_taken,
15461561
stream=stream,
15471562
stream_cls=stream_cls,
15481563
response_headers=None,
@@ -1553,11 +1568,11 @@ async def _request(
15531568
except Exception as err:
15541569
log.debug("Encountered Exception", exc_info=True)
15551570

1556-
if retries > 0:
1571+
if retries_taken > 0:
15571572
return await self._retry_request(
15581573
input_options,
15591574
cast_to,
1560-
retries,
1575+
retries_taken=retries_taken,
15611576
stream=stream,
15621577
stream_cls=stream_cls,
15631578
response_headers=None,
@@ -1575,13 +1590,13 @@ async def _request(
15751590
except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
15761591
log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
15771592

1578-
if retries > 0 and self._should_retry(err.response):
1593+
if remaining_retries > 0 and self._should_retry(err.response):
15791594
await err.response.aclose()
15801595
return await self._retry_request(
15811596
input_options,
15821597
cast_to,
1583-
retries,
1584-
err.response.headers,
1598+
retries_taken=retries_taken,
1599+
response_headers=err.response.headers,
15851600
stream=stream,
15861601
stream_cls=stream_cls,
15871602
)
@@ -1600,34 +1615,34 @@ async def _request(
16001615
response=response,
16011616
stream=stream,
16021617
stream_cls=stream_cls,
1603-
retries_taken=options.get_max_retries(self.max_retries) - retries,
1618+
retries_taken=retries_taken,
16041619
)
16051620

16061621
async def _retry_request(
16071622
self,
16081623
options: FinalRequestOptions,
16091624
cast_to: Type[ResponseT],
1610-
remaining_retries: int,
1611-
response_headers: httpx.Headers | None,
16121625
*,
1626+
retries_taken: int,
1627+
response_headers: httpx.Headers | None,
16131628
stream: bool,
16141629
stream_cls: type[_AsyncStreamT] | None,
16151630
) -> ResponseT | _AsyncStreamT:
1616-
remaining = remaining_retries - 1
1617-
if remaining == 1:
1631+
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
1632+
if remaining_retries == 1:
16181633
log.debug("1 retry left")
16191634
else:
1620-
log.debug("%i retries left", remaining)
1635+
log.debug("%i retries left", remaining_retries)
16211636

1622-
timeout = self._calculate_retry_timeout(remaining, options, response_headers)
1637+
timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
16231638
log.info("Retrying request to %s in %f seconds", options.url, timeout)
16241639

16251640
await anyio.sleep(timeout)
16261641

16271642
return await self._request(
16281643
options=options,
16291644
cast_to=cast_to,
1630-
remaining_retries=remaining,
1645+
retries_taken=retries_taken + 1,
16311646
stream=stream,
16321647
stream_cls=stream_cls,
16331648
)

src/onebusaway/_compat.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,14 @@ def model_dump(
136136
exclude: IncEx = None,
137137
exclude_unset: bool = False,
138138
exclude_defaults: bool = False,
139+
warnings: bool = True,
139140
) -> dict[str, Any]:
140141
if PYDANTIC_V2:
141142
return model.model_dump(
142143
exclude=exclude,
143144
exclude_unset=exclude_unset,
144145
exclude_defaults=exclude_defaults,
146+
warnings=warnings,
145147
)
146148
return cast(
147149
"dict[str, Any]",

src/onebusaway/_utils/_utils.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,12 +363,13 @@ def file_from_path(path: str) -> FileTypes:
363363

364364
def get_required_header(headers: HeadersLike, header: str) -> str:
365365
lower_header = header.lower()
366-
if isinstance(headers, Mapping):
367-
for k, v in headers.items():
366+
if is_mapping_t(headers):
367+
# mypy doesn't understand the type narrowing here
368+
for k, v in headers.items(): # type: ignore
368369
if k.lower() == lower_header and isinstance(v, str):
369370
return v
370371

371-
""" to deal with the case where the header looks like Stainless-Event-Id """
372+
# to deal with the case where the header looks like Stainless-Event-Id
372373
intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize())
373374

374375
for normalized_header in [header, lower_header, header.upper(), intercaps_header]:

0 commit comments

Comments
 (0)