diff --git a/.release-please-manifest.json b/.release-please-manifest.json index cda9cbd..4c5a1a0 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.2" + ".": "0.1.3" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 52aa5e6..f3fe7e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.1.3 (2025-02-14) + +Full Changelog: [v0.1.2...v0.1.3](https://github.com/gitpod-io/gitpod-sdk-python/compare/v0.1.2...v0.1.3) + +### Chores + +* **internal:** add jsonl unit tests ([#43](https://github.com/gitpod-io/gitpod-sdk-python/issues/43)) ([8641b53](https://github.com/gitpod-io/gitpod-sdk-python/commit/8641b53116b20dd8e329f229afd84ec6a100fcef)) +* **internal:** codegen related update ([#41](https://github.com/gitpod-io/gitpod-sdk-python/issues/41)) ([e5dceda](https://github.com/gitpod-io/gitpod-sdk-python/commit/e5dceda109bb01cf538dce83c3ab16d60461eb3d)) + ## 0.1.2 (2025-02-14) Full Changelog: [v0.1.1...v0.1.2](https://github.com/gitpod-io/gitpod-sdk-python/compare/v0.1.1...v0.1.2) diff --git a/pyproject.toml b/pyproject.toml index ec2ef5b..c1a5709 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "gitpod-sdk" -version = "0.1.2" +version = "0.1.3" description = "The official Python library for the gitpod API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/gitpod/_exceptions.py b/src/gitpod/_exceptions.py index e78213c..a880564 100644 --- a/src/gitpod/_exceptions.py +++ b/src/gitpod/_exceptions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, cast +from typing import Any, Optional, cast from typing_extensions import Literal import httpx @@ -53,11 +53,6 @@ class APIError(GitpodError): Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. """ - if TYPE_CHECKING: - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: super().__init__(message) diff --git a/src/gitpod/_version.py b/src/gitpod/_version.py index b9d4fe7..a4b0bd6 100644 --- a/src/gitpod/_version.py +++ b/src/gitpod/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "gitpod" -__version__ = "0.1.2" # x-release-please-version +__version__ = "0.1.3" # x-release-please-version diff --git a/tests/decoders/test_jsonl.py b/tests/decoders/test_jsonl.py new file mode 100644 index 0000000..a14a93e --- /dev/null +++ b/tests/decoders/test_jsonl.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +from typing import Any, Iterator, AsyncIterator +from typing_extensions import TypeVar + +import httpx +import pytest + +from gitpod._decoders.jsonl import JSONLDecoder, AsyncJSONLDecoder + +_T = TypeVar("_T") + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_basic(sync: bool) -> None: + def body() -> Iterator[bytes]: + yield b'{"foo":true}\n' + yield b'{"bar":false}\n' + + iterator = make_jsonl_iterator( + content=body(), + sync=sync, + line_type=object, + ) + + assert await iter_next(iterator) == {"foo": True} + assert await iter_next(iterator) == {"bar": False} + + await assert_empty_iter(iterator) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_new_lines_in_json( + sync: bool, +) -> None: + def body() -> Iterator[bytes]: + yield b'{"content":"Hello, world!\\nHow are you doing?"}' + + iterator = make_jsonl_iterator(content=body(), sync=sync, line_type=object) + + assert await iter_next(iterator) == {"content": "Hello, world!\nHow are you doing?"} + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multi_byte_character_multiple_chunks( + sync: bool, +) -> None: + def body() -> Iterator[bytes]: + yield b'{"content":"' + # bytes taken from the string 'известни' and arbitrarily split + # so that some multi-byte characters span multiple chunks + yield b"\xd0" + yield b"\xb8\xd0\xb7\xd0" + yield b"\xb2\xd0\xb5\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8" + yield b'"}\n' + + iterator = make_jsonl_iterator(content=body(), sync=sync, line_type=object) + + assert await iter_next(iterator) == {"content": "известни"} + + +async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]: + for chunk in iter: + yield chunk + + +async def iter_next(iter: Iterator[_T] | AsyncIterator[_T]) -> _T: + if isinstance(iter, AsyncIterator): + return await iter.__anext__() + return next(iter) + + +async def assert_empty_iter(decoder: JSONLDecoder[Any] | AsyncJSONLDecoder[Any]) -> None: + with pytest.raises((StopAsyncIteration, RuntimeError)): + await iter_next(decoder) + + +def make_jsonl_iterator( + content: Iterator[bytes], + *, + sync: bool, + line_type: type[_T], +) -> JSONLDecoder[_T] | AsyncJSONLDecoder[_T]: + if sync: + return JSONLDecoder(line_type=line_type, raw_iterator=content, http_response=httpx.Response(200)) + + return AsyncJSONLDecoder(line_type=line_type, raw_iterator=to_aiter(content), http_response=httpx.Response(200))