Skip to content

Commit 1972874

Browse files
committed
Release 0.0.319
1 parent a3a4df4 commit 1972874

14 files changed

Lines changed: 384 additions & 313 deletions

.fern/metadata.json

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
{
22
"cliVersion": "5.7.9",
33
"generatorName": "fernapi/fern-python-sdk",
4-
"generatorVersion": "5.3.6",
4+
"generatorVersion": "5.10.0",
55
"generatorConfig": {
66
"client_class_name": "payabli",
7-
"enable_wire_tests": false
7+
"enable_wire_tests": false,
8+
"include_union_utils": true
89
},
9-
"originGitCommit": "d69946f7f37797d4fdc34de0c29dc377dbc94fc5",
10-
"sdkVersion": "0.0.318"
10+
"originGitCommit": "7082e1f2f215e55d567b769dcf0f9732ab232c4d",
11+
"originGitCommitIsDirty": true,
12+
"invokedBy": "ci",
13+
"requestedVersion": "0.0.319",
14+
"ciProvider": "github",
15+
"sdkVersion": "0.0.319"
1116
}

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,21 @@ The SDK is instrumented with automatic retries with exponential backoff. A reque
189189
as the request is deemed retryable and the number of retry attempts has not grown larger than the configured
190190
retry limit (default: 2).
191191

192-
A request is deemed retryable when any of the following HTTP status codes is returned:
192+
Which status codes are retried depends on the `retryStatusCodes` generator configuration:
193193

194+
**`legacy`** (current default): retries on
194195
- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout)
196+
- [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) (Conflict)
195197
- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests)
196-
- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors)
198+
- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses) (All server errors, including 500)
199+
200+
**`recommended`**: retries on
201+
- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout)
202+
- [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) (Conflict)
203+
- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests)
204+
- [502](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502) (Bad Gateway)
205+
- [503](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) (Service Unavailable)
206+
- [504](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504) (Gateway Timeout)
197207

198208
Use the `max_retries` request option to configure this behavior.
199209

poetry.lock

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

pyproject.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ dynamic = ["version"]
44

55
[tool.poetry]
66
name = "payabli"
7-
version = "0.0.318"
7+
version = "0.0.319"
88
description = ""
99
readme = "README.md"
1010
authors = []
@@ -37,20 +37,21 @@ Repository = 'https://github.com/payabli/sdk-python'
3737

3838
[tool.poetry.dependencies]
3939
python = "^3.10"
40-
aiohttp = { version = ">=3.10.0,<4", optional = true}
40+
aiohttp = { version = ">=3.13.4,<4", optional = true, python = ">=3.9"}
4141
httpx = ">=0.21.2"
42-
httpx-aiohttp = { version = "0.1.8", optional = true}
42+
httpx-aiohttp = { version = "0.1.8", optional = true, python = ">=3.9"}
4343
pydantic = ">= 1.9.2"
44-
pydantic-core = ">=2.18.2,<2.44.0"
44+
pydantic-core = ">=2.18.2,<3.0.0"
4545
typing_extensions = ">= 4.0.0"
4646

4747
[tool.poetry.group.dev.dependencies]
4848
mypy = "==1.13.0"
49-
pytest = "^8.2.0"
49+
pytest = "^9.0.3"
5050
pytest-asyncio = "^1.0.0"
5151
pytest-xdist = "^3.6.1"
5252
python-dateutil = "^2.9.0"
5353
types-python-dateutil = "^2.9.0.20240316"
54+
urllib3 = ">=2.6.3,<3.0.0"
5455
ruff = "==0.11.5"
5556

5657
[tool.pytest.ini_options]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
httpx>=0.21.2
22
pydantic>= 1.9.2
3-
pydantic-core>=2.18.2,<2.44.0
3+
pydantic-core>=2.18.2,<3.0.0
44
typing_extensions>= 4.0.0

src/payabli/core/client_wrapper.py

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

2929
headers: typing.Dict[str, str] = {
30-
"User-Agent": "payabli/0.0.318",
30+
"User-Agent": "payabli/0.0.319",
3131
"X-Fern-Language": "Python",
3232
"X-Fern-Runtime": f"python/{platform.python_version()}",
3333
"X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",
3434
"X-Fern-SDK-Name": "payabli",
35-
"X-Fern-SDK-Version": "0.0.318",
35+
"X-Fern-SDK-Version": "0.0.319",
3636
**(self.get_custom_headers() or {}),
3737
}
3838
headers["requestToken"] = self.api_key

src/payabli/core/http_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,7 @@ def _retry_timeout_from_retries(retries: int) -> float:
125125

126126

127127
def _should_retry(response: httpx.Response) -> bool:
128-
retryable_400s = [429, 408, 409]
129-
return response.status_code >= 500 or response.status_code in retryable_400s
128+
return response.status_code >= 500 or response.status_code in [429, 408, 409]
130129

131130

132131
_SENSITIVE_HEADERS = frozenset(

src/payabli/core/http_sse/_api.py

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# This file was auto-generated by Fern from our API Definition.
22

3+
import codecs
34
import re
45
from contextlib import asynccontextmanager, contextmanager
5-
from typing import Any, AsyncGenerator, AsyncIterator, Iterator, cast
6+
from typing import Any, AsyncGenerator, AsyncIterator, Iterator
67

78
import httpx
89
from ._decoders import SSEDecoder
@@ -45,46 +46,81 @@ def _get_charset(self) -> str:
4546
def response(self) -> httpx.Response:
4647
return self._response
4748

49+
@staticmethod
50+
def _normalize_sse_line_endings(buf: str) -> str:
51+
"""Normalize line endings per the SSE spec (\\r\\n → \\n, bare \\r → \\n).
52+
53+
A trailing \\r is preserved because it may pair with a leading \\n in
54+
the next chunk to form a single \\r\\n terminator.
55+
"""
56+
buf = buf.replace("\r\n", "\n")
57+
if buf.endswith("\r"):
58+
return buf[:-1].replace("\r", "\n") + "\r"
59+
return buf.replace("\r", "\n")
60+
4861
def iter_sse(self) -> Iterator[ServerSentEvent]:
4962
self._check_content_type()
5063
decoder = SSEDecoder()
5164
charset = self._get_charset()
65+
text_decoder = codecs.getincrementaldecoder(charset)(errors="replace")
5266

53-
buffer = ""
67+
buf = ""
5468
for chunk in self._response.iter_bytes():
55-
# Decode chunk using detected charset
56-
text_chunk = chunk.decode(charset, errors="replace")
57-
buffer += text_chunk
58-
59-
# Process complete lines
60-
while "\n" in buffer:
61-
line, buffer = buffer.split("\n", 1)
62-
line = line.rstrip("\r")
69+
buf += text_decoder.decode(chunk)
70+
buf = self._normalize_sse_line_endings(buf)
71+
72+
while "\n" in buf:
73+
line, buf = buf.split("\n", 1)
6374
sse = decoder.decode(line)
64-
# when we reach a "\n\n" => line = ''
65-
# => decoder will attempt to return an SSE Event
6675
if sse is not None:
6776
yield sse
6877

69-
# Process any remaining data in buffer
70-
if buffer.strip():
71-
line = buffer.rstrip("\r")
78+
# Flush any remaining bytes from the incremental decoder
79+
buf += text_decoder.decode(b"", final=True)
80+
buf = buf.replace("\r\n", "\n").replace("\r", "\n")
81+
82+
while "\n" in buf:
83+
line, buf = buf.split("\n", 1)
7284
sse = decoder.decode(line)
7385
if sse is not None:
7486
yield sse
7587

88+
if buf.strip():
89+
sse = decoder.decode(buf)
90+
if sse is not None:
91+
yield sse
92+
7693
async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]:
7794
self._check_content_type()
7895
decoder = SSEDecoder()
79-
lines = cast(AsyncGenerator[str, None], self._response.aiter_lines())
80-
try:
81-
async for line in lines:
82-
line = line.rstrip("\n")
96+
charset = self._get_charset()
97+
text_decoder = codecs.getincrementaldecoder(charset)(errors="replace")
98+
99+
buf = ""
100+
async for chunk in self._response.aiter_bytes():
101+
buf += text_decoder.decode(chunk)
102+
buf = self._normalize_sse_line_endings(buf)
103+
104+
while "\n" in buf:
105+
line, buf = buf.split("\n", 1)
83106
sse = decoder.decode(line)
84107
if sse is not None:
85108
yield sse
86-
finally:
87-
await lines.aclose()
109+
110+
# Flush any remaining bytes from the incremental decoder
111+
buf += text_decoder.decode(b"", final=True)
112+
buf = buf.replace("\r\n", "\n").replace("\r", "\n")
113+
114+
while "\n" in buf:
115+
line, buf = buf.split("\n", 1)
116+
sse = decoder.decode(line)
117+
if sse is not None:
118+
yield sse
119+
120+
if buf.strip():
121+
sse = decoder.decode(buf)
122+
if sse is not None:
123+
yield sse
88124

89125

90126
@contextmanager

src/payabli/payout_subscription/types/get_payout_subscription_response.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ class GetPayoutSubscriptionResponse(UniversalBaseModel):
4646
event_time=datetime.datetime.fromisoformat(
4747
"2025-09-01 06:00:00+00:00",
4848
),
49-
extra_data={"key": "value"},
5049
ref_data="refData",
5150
source="api",
5251
)

src/payabli/payout_subscription/types/payout_subscription_query_record.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ class PayoutSubscriptionQueryRecord(UniversalBaseModel):
5252
event_time=datetime.datetime.fromisoformat(
5353
"2025-09-01 06:00:00+00:00",
5454
),
55-
extra_data={"key": "value"},
5655
ref_data="refData",
5756
source="api",
5857
)

0 commit comments

Comments
 (0)