From 10566c38470e198dddd7f56f17cd6147e1250afe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:36:42 -0800 Subject: [PATCH] release: 0.16.0 (#178) Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +-- .release-please-manifest.json | 2 +- .stats.yml | 2 +- CHANGELOG.md | 21 ++++++++++++++++ mypy.ini | 2 +- pyproject.toml | 3 ++- requirements-dev.lock | 4 +-- scripts/bootstrap | 2 +- scripts/lint | 1 - src/groq/_response.py | 12 ++++++--- src/groq/_version.py | 2 +- src/groq/resources/audio/audio.py | 4 +-- src/groq/resources/audio/transcriptions.py | 4 +-- src/groq/resources/audio/translations.py | 4 +-- src/groq/resources/chat/chat.py | 4 +-- src/groq/resources/chat/completions.py | 18 +++++++++++-- src/groq/resources/embeddings.py | 4 +-- src/groq/resources/models.py | 4 +-- .../types/chat/completion_create_params.py | 3 +++ tests/api_resources/chat/test_completions.py | 2 ++ tests/test_client.py | 25 +++++++++++++------ 21 files changed, 91 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4029396..c8a8a4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,6 @@ jobs: lint: name: lint runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 @@ -30,6 +29,7 @@ jobs: - name: Run lints run: ./scripts/lint + test: name: test runs-on: ubuntu-latest @@ -50,4 +50,3 @@ jobs: - name: Run tests run: ./scripts/test - diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8f3e0a4..b4e9013 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.15.0" + ".": "0.16.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 80c8008..75e7382 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/groqcloud%2Fgroqcloud-8bf31041292f851076489c3ac1270d06c49b995225d91cf5de2288a4bcfa8c29.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/groqcloud%2Fgroqcloud-c5b2589925cd383cce5899869bbccbce45a90bd7c7c9608a4a9d087a88be9f4a.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 563a5ca..a26ee2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 0.16.0 (2025-01-29) + +Full Changelog: [v0.15.0...v0.16.0](https://github.com/groq/groq-python/compare/v0.15.0...v0.16.0) + +### Features + +* **api:** api update ([#183](https://github.com/groq/groq-python/issues/183)) ([a5cdbc5](https://github.com/groq/groq-python/commit/a5cdbc5af797bdb3aa9733c60f733e612d619ef5)) + + +### Chores + +* **internal:** codegen related update ([#177](https://github.com/groq/groq-python/issues/177)) ([01e6304](https://github.com/groq/groq-python/commit/01e63041c81099bf0fb64a372462be71767fb747)) +* **internal:** codegen related update ([#180](https://github.com/groq/groq-python/issues/180)) ([5c8db1a](https://github.com/groq/groq-python/commit/5c8db1a9795dfad7316a9c1e026386a0ad62e3db)) +* **internal:** minor formatting changes ([#182](https://github.com/groq/groq-python/issues/182)) ([2c4e409](https://github.com/groq/groq-python/commit/2c4e409fe047cb2cc9ca3805f79244c1fdbb7cf0)) +* **internal:** minor style changes ([#181](https://github.com/groq/groq-python/issues/181)) ([77c752a](https://github.com/groq/groq-python/commit/77c752ab1a635b675743baf02d4896439bc85a07)) + + +### Documentation + +* **raw responses:** fix duplicate `the` ([#179](https://github.com/groq/groq-python/issues/179)) ([a28cbd8](https://github.com/groq/groq-python/commit/a28cbd863d875954a0404ff1148da02cd131de98)) + ## 0.15.0 (2025-01-11) Full Changelog: [v0.14.0...v0.15.0](https://github.com/groq/groq-python/compare/v0.14.0...v0.15.0) diff --git a/mypy.ini b/mypy.ini index 971bfe9..33e5210 100644 --- a/mypy.ini +++ b/mypy.ini @@ -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] diff --git a/pyproject.toml b/pyproject.toml index 4acc8aa..d22e5f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "groq" -version = "0.15.0" +version = "0.16.0" description = "The official Python library for the groq API" dynamic = ["readme"] license = "Apache-2.0" @@ -129,6 +129,7 @@ testpaths = ["tests"] addopts = "--tb=short" xfail_strict = true asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" filterwarnings = [ "error" ] diff --git a/requirements-dev.lock b/requirements-dev.lock index 94ac99e..f8200c8 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,7 +48,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 @@ -68,7 +68,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 diff --git a/scripts/bootstrap b/scripts/bootstrap index 8c5c60e..e84fe62 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -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 diff --git a/scripts/lint b/scripts/lint index a65cb43..4e30547 100755 --- a/scripts/lint +++ b/scripts/lint @@ -9,4 +9,3 @@ rye run lint echo "==> Making sure it imports" rye run python -c 'import groq' - diff --git a/src/groq/_response.py b/src/groq/_response.py index d28f5ad..1396522 100644 --- a/src/groq/_response.py +++ b/src/groq/_response.py @@ -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): @@ -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`") @@ -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 groq import BaseModel`") if ( diff --git a/src/groq/_version.py b/src/groq/_version.py index fd89b08..971fc01 100644 --- a/src/groq/_version.py +++ b/src/groq/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "groq" -__version__ = "0.15.0" # x-release-please-version +__version__ = "0.16.0" # x-release-please-version diff --git a/src/groq/resources/audio/audio.py b/src/groq/resources/audio/audio.py index f33b8c2..3c5f05b 100644 --- a/src/groq/resources/audio/audio.py +++ b/src/groq/resources/audio/audio.py @@ -36,7 +36,7 @@ def translations(self) -> Translations: @cached_property def with_raw_response(self) -> AudioWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers @@ -65,7 +65,7 @@ def translations(self) -> AsyncTranslations: @cached_property def with_raw_response(self) -> AsyncAudioWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers diff --git a/src/groq/resources/audio/transcriptions.py b/src/groq/resources/audio/transcriptions.py index c67d778..65700d0 100644 --- a/src/groq/resources/audio/transcriptions.py +++ b/src/groq/resources/audio/transcriptions.py @@ -33,7 +33,7 @@ class Transcriptions(SyncAPIResource): @cached_property def with_raw_response(self) -> TranscriptionsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers @@ -242,7 +242,7 @@ class AsyncTranscriptions(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTranscriptionsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers diff --git a/src/groq/resources/audio/translations.py b/src/groq/resources/audio/translations.py index e689512..0fe60a5 100644 --- a/src/groq/resources/audio/translations.py +++ b/src/groq/resources/audio/translations.py @@ -33,7 +33,7 @@ class Translations(SyncAPIResource): @cached_property def with_raw_response(self) -> TranslationsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers @@ -123,7 +123,7 @@ class AsyncTranslations(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTranslationsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers diff --git a/src/groq/resources/chat/chat.py b/src/groq/resources/chat/chat.py index 1b2ea5a..3706a08 100644 --- a/src/groq/resources/chat/chat.py +++ b/src/groq/resources/chat/chat.py @@ -24,7 +24,7 @@ def completions(self) -> Completions: @cached_property def with_raw_response(self) -> ChatWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers @@ -49,7 +49,7 @@ def completions(self) -> AsyncCompletions: @cached_property def with_raw_response(self) -> AsyncChatWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers diff --git a/src/groq/resources/chat/completions.py b/src/groq/resources/chat/completions.py index ea8a1ef..6ca0b1d 100644 --- a/src/groq/resources/chat/completions.py +++ b/src/groq/resources/chat/completions.py @@ -36,7 +36,7 @@ class Completions(SyncAPIResource): @cached_property def with_raw_response(self) -> CompletionsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers @@ -68,6 +68,7 @@ def create( n: Optional[int] | NotGiven = NOT_GIVEN, parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] | NotGiven = NOT_GIVEN, response_format: Optional[completion_create_params.ResponseFormat] | NotGiven = NOT_GIVEN, seed: Optional[int] | NotGiven = NOT_GIVEN, service_tier: Optional[Literal["auto", "on_demand", "flex"]] | NotGiven = NOT_GIVEN, @@ -104,6 +105,7 @@ def create( n: Optional[int] | NotGiven = NOT_GIVEN, parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] | NotGiven = NOT_GIVEN, response_format: Optional[completion_create_params.ResponseFormat] | NotGiven = NOT_GIVEN, seed: Optional[int] | NotGiven = NOT_GIVEN, service_tier: Optional[Literal["auto", "on_demand", "flex"]] | NotGiven = NOT_GIVEN, @@ -140,6 +142,7 @@ def create( n: Optional[int] | NotGiven = NOT_GIVEN, parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] | NotGiven = NOT_GIVEN, response_format: Optional[completion_create_params.ResponseFormat] | NotGiven = NOT_GIVEN, seed: Optional[int] | NotGiven = NOT_GIVEN, service_tier: Optional[Literal["auto", "on_demand", "flex"]] | NotGiven = NOT_GIVEN, @@ -175,6 +178,7 @@ def create( n: Optional[int] | NotGiven = NOT_GIVEN, parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] | NotGiven = NOT_GIVEN, response_format: Optional[completion_create_params.ResponseFormat] | NotGiven = NOT_GIVEN, seed: Optional[int] | NotGiven = NOT_GIVEN, service_tier: Optional[Literal["auto", "on_demand", "flex"]] | NotGiven = NOT_GIVEN, @@ -246,6 +250,8 @@ def create( whether they appear in the text so far, increasing the model's likelihood to talk about new topics. + reasoning_format: Specifies how to output reasoning tokens + response_format: An object specifying the format that the model must output. Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the @@ -329,6 +335,7 @@ def create( "n": n, "parallel_tool_calls": parallel_tool_calls, "presence_penalty": presence_penalty, + "reasoning_format": reasoning_format, "response_format": response_format, "seed": seed, "service_tier": service_tier, @@ -356,7 +363,7 @@ class AsyncCompletions(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncCompletionsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers @@ -388,6 +395,7 @@ async def create( n: Optional[int] | NotGiven = NOT_GIVEN, parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] | NotGiven = NOT_GIVEN, response_format: Optional[completion_create_params.ResponseFormat] | NotGiven = NOT_GIVEN, seed: Optional[int] | NotGiven = NOT_GIVEN, service_tier: Optional[Literal["auto", "on_demand", "flex"]] | NotGiven = NOT_GIVEN, @@ -424,6 +432,7 @@ async def create( n: Optional[int] | NotGiven = NOT_GIVEN, parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] | NotGiven = NOT_GIVEN, response_format: Optional[completion_create_params.ResponseFormat] | NotGiven = NOT_GIVEN, seed: Optional[int] | NotGiven = NOT_GIVEN, service_tier: Optional[Literal["auto", "on_demand", "flex"]] | NotGiven = NOT_GIVEN, @@ -460,6 +469,7 @@ async def create( n: Optional[int] | NotGiven = NOT_GIVEN, parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] | NotGiven = NOT_GIVEN, response_format: Optional[completion_create_params.ResponseFormat] | NotGiven = NOT_GIVEN, seed: Optional[int] | NotGiven = NOT_GIVEN, service_tier: Optional[Literal["auto", "on_demand", "flex"]] | NotGiven = NOT_GIVEN, @@ -495,6 +505,7 @@ async def create( n: Optional[int] | NotGiven = NOT_GIVEN, parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, presence_penalty: Optional[float] | NotGiven = NOT_GIVEN, + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] | NotGiven = NOT_GIVEN, response_format: Optional[completion_create_params.ResponseFormat] | NotGiven = NOT_GIVEN, seed: Optional[int] | NotGiven = NOT_GIVEN, service_tier: Optional[Literal["auto", "on_demand", "flex"]] | NotGiven = NOT_GIVEN, @@ -566,6 +577,8 @@ async def create( whether they appear in the text so far, increasing the model's likelihood to talk about new topics. + reasoning_format: Specifies how to output reasoning tokens + response_format: An object specifying the format that the model must output. Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the @@ -649,6 +662,7 @@ async def create( "n": n, "parallel_tool_calls": parallel_tool_calls, "presence_penalty": presence_penalty, + "reasoning_format": reasoning_format, "response_format": response_format, "seed": seed, "service_tier": service_tier, diff --git a/src/groq/resources/embeddings.py b/src/groq/resources/embeddings.py index 8e439dd..ecda0aa 100644 --- a/src/groq/resources/embeddings.py +++ b/src/groq/resources/embeddings.py @@ -31,7 +31,7 @@ class Embeddings(SyncAPIResource): @cached_property def with_raw_response(self) -> EmbeddingsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers @@ -107,7 +107,7 @@ class AsyncEmbeddings(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncEmbeddingsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers diff --git a/src/groq/resources/models.py b/src/groq/resources/models.py index 0cb9c71..d6a2313 100644 --- a/src/groq/resources/models.py +++ b/src/groq/resources/models.py @@ -25,7 +25,7 @@ class Models(SyncAPIResource): @cached_property def with_raw_response(self) -> ModelsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers @@ -131,7 +131,7 @@ class AsyncModels(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncModelsWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/groq/groq-python#accessing-raw-response-data-eg-headers diff --git a/src/groq/types/chat/completion_create_params.py b/src/groq/types/chat/completion_create_params.py index 030c1fb..8268b8d 100644 --- a/src/groq/types/chat/completion_create_params.py +++ b/src/groq/types/chat/completion_create_params.py @@ -95,6 +95,9 @@ class CompletionCreateParams(TypedDict, total=False): far, increasing the model's likelihood to talk about new topics. """ + reasoning_format: Optional[Literal["hidden", "raw", "parsed"]] + """Specifies how to output reasoning tokens""" + response_format: Optional[ResponseFormat] """An object specifying the format that the model must output. diff --git a/tests/api_resources/chat/test_completions.py b/tests/api_resources/chat/test_completions.py index dea3230..1e43126 100644 --- a/tests/api_resources/chat/test_completions.py +++ b/tests/api_resources/chat/test_completions.py @@ -57,6 +57,7 @@ def test_method_create_with_all_params(self, client: Groq) -> None: n=1, parallel_tool_calls=True, presence_penalty=-2, + reasoning_format="hidden", response_format={"type": "text"}, seed=0, service_tier="auto", @@ -160,6 +161,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncGroq) -> N n=1, parallel_tool_calls=True, presence_penalty=-2, + reasoning_format="hidden", response_format={"type": "text"}, seed=0, service_tier="auto", diff --git a/tests/test_client.py b/tests/test_client.py index 3e66af6..1615f63 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,6 +6,7 @@ import os import sys import json +import time import asyncio import inspect import subprocess @@ -1708,10 +1709,20 @@ async def test_main() -> None: [sys.executable, "-c", test_code], text=True, ) as process: - try: - process.wait(2) - if process.returncode: - raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") - except subprocess.TimeoutExpired as e: - process.kill() - raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e + timeout = 10 # seconds + + start_time = time.monotonic() + while True: + return_code = process.poll() + if return_code is not None: + if return_code != 0: + raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") + + # success + break + + if time.monotonic() - start_time > timeout: + process.kill() + raise AssertionError("calling get_platform using asyncify resulted in a hung process") + + time.sleep(0.1)