diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 3e2bf49..2aca35a 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.4.1"
+ ".": "0.5.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1df0ee..3604650 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
# Changelog
+## 0.5.0 (2026-02-25)
+
+Full Changelog: [v0.4.1...v0.5.0](https://github.com/parallel-web/parallel-sdk-python/compare/v0.4.1...v0.5.0)
+
+### Features
+
+* **client:** add custom JSON encoder for extended type support ([b2c8bf9](https://github.com/parallel-web/parallel-sdk-python/commit/b2c8bf9b8246e2e8f1d53a7c8e238dd19b727a77))
+
+
+### Chores
+
+* format all `api.md` files ([b74b93b](https://github.com/parallel-web/parallel-sdk-python/commit/b74b93bf04d678cc283b8f312a3a4c5bb314c468))
+* **internal:** add request options to SSE classes ([00dbc30](https://github.com/parallel-web/parallel-sdk-python/commit/00dbc3027e59adda51eb623d6a724501f70a7720))
+* **internal:** bump dependencies ([f49c841](https://github.com/parallel-web/parallel-sdk-python/commit/f49c841670d88f8fc38e0a17f19242f7570a9aad))
+* **internal:** fix lint error on Python 3.14 ([cb3f364](https://github.com/parallel-web/parallel-sdk-python/commit/cb3f3645bc67b98235a732f357d8a24fdf164032))
+* **internal:** make `test_proxy_environment_variables` more resilient ([d3ba149](https://github.com/parallel-web/parallel-sdk-python/commit/d3ba149917deab7ee28a3f38bd7b1f3f4bd2b9c6))
+* **internal:** make `test_proxy_environment_variables` more resilient to env ([1e1d858](https://github.com/parallel-web/parallel-sdk-python/commit/1e1d858e7c21785744d94ceef33e655dcf75eacc))
+* update mock server docs ([028965c](https://github.com/parallel-web/parallel-sdk-python/commit/028965c0b2868051617b266493466f9fc7816705))
+
## 0.4.1 (2026-01-28)
Full Changelog: [v0.4.0...v0.4.1](https://github.com/parallel-web/parallel-sdk-python/compare/v0.4.0...v0.4.1)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6bde9f8..3276e79 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -88,8 +88,7 @@ $ pip install ./path-to-wheel-file.whl
Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
```sh
-# you will need npm installed
-$ npx prism mock path/to/your/openapi.yml
+$ ./scripts/mock
```
```sh
diff --git a/api.md b/api.md
index e00a252..66e3651 100644
--- a/api.md
+++ b/api.md
@@ -35,102 +35,4 @@ Convenience methods:
- client.task_run.execute(input, processor, output: OutputSchema) -> TaskRunResult
- client.task_run.execute(input, processor, output: Type[OutputT]) -> ParsedTaskRunResult[OutputT]
-# Beta
-
-Types:
-
-```python
-from parallel.types.beta import (
- ExcerptSettings,
- ExtractError,
- ExtractResponse,
- ExtractResult,
- FetchPolicy,
- SearchResult,
- UsageItem,
- WebSearchResult,
-)
-```
-
-Methods:
-
-- client.beta.extract(\*\*params) -> ExtractResponse
-- client.beta.search(\*\*params) -> SearchResult
-
-## TaskRun
-
-Types:
-
-```python
-from parallel.types.beta import (
- BetaRunInput,
- BetaTaskRunResult,
- ErrorEvent,
- McpServer,
- McpToolCall,
- ParallelBeta,
- TaskRunEvent,
- Webhook,
- TaskRunEventsResponse,
-)
-```
-
-Methods:
-
-- client.beta.task_run.create(\*\*params) -> TaskRun
-- client.beta.task_run.events(run_id) -> TaskRunEventsResponse
-- client.beta.task_run.result(run_id, \*\*params) -> BetaTaskRunResult
-
-## TaskGroup
-
-Types:
-
-```python
-from parallel.types.beta import (
- TaskGroup,
- TaskGroupRunResponse,
- TaskGroupStatus,
- TaskGroupEventsResponse,
- TaskGroupGetRunsResponse,
-)
-```
-
-Methods:
-
-- client.beta.task_group.create(\*\*params) -> TaskGroup
-- client.beta.task_group.retrieve(task_group_id) -> TaskGroup
-- client.beta.task_group.add_runs(task_group_id, \*\*params) -> TaskGroupRunResponse
-- client.beta.task_group.events(task_group_id, \*\*params) -> TaskGroupEventsResponse
-- client.beta.task_group.get_runs(task_group_id, \*\*params) -> TaskGroupGetRunsResponse
-
-## FindAll
-
-Types:
-
-```python
-from parallel.types.beta import (
- FindAllCandidateMatchStatusEvent,
- FindAllEnrichInput,
- FindAllExtendInput,
- FindAllRun,
- FindAllRunInput,
- FindAllRunResult,
- FindAllRunStatusEvent,
- FindAllSchema,
- FindAllSchemaUpdatedEvent,
- IngestInput,
- FindAllEventsResponse,
-)
-```
-
-Methods:
-
-- client.beta.findall.create(\*\*params) -> FindAllRun
-- client.beta.findall.retrieve(findall_id) -> FindAllRun
-- client.beta.findall.cancel(findall_id) -> object
-- client.beta.findall.enrich(findall_id, \*\*params) -> FindAllSchema
-- client.beta.findall.events(findall_id, \*\*params) -> FindAllEventsResponse
-- client.beta.findall.extend(findall_id, \*\*params) -> FindAllSchema
-- client.beta.findall.ingest(\*\*params) -> FindAllSchema
-- client.beta.findall.result(findall_id) -> FindAllRunResult
-- client.beta.findall.schema(findall_id) -> FindAllSchema
+# [Beta](src/parallel/resources/beta/api.md)
diff --git a/pyproject.toml b/pyproject.toml
index c1f6b0c..089b000 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "parallel-web"
-version = "0.4.1"
+version = "0.5.0"
description = "The official Python library for the Parallel API"
dynamic = ["readme"]
license = "MIT"
@@ -70,7 +70,7 @@ format = { chain = [
# run formatting again to fix any inconsistencies when imports are stripped
"format:ruff",
]}
-"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md"
+"format:docs" = "bash -c 'python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md)'"
"format:ruff" = "ruff format"
"lint" = { chain = [
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 6b24ce1..c32a9d9 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -12,14 +12,14 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
-aiohttp==3.13.2
+aiohttp==3.13.3
# via httpx-aiohttp
# via parallel-web
aiosignal==1.4.0
# via aiohttp
annotated-types==0.7.0
# via pydantic
-anyio==4.12.0
+anyio==4.12.1
# via httpx
# via parallel-web
argcomplete==3.6.3
@@ -31,7 +31,7 @@ attrs==25.4.0
# via nox
backports-asyncio-runner==1.2.0
# via pytest-asyncio
-certifi==2025.11.12
+certifi==2026.1.4
# via httpcore
# via httpx
colorama==0.4.6
@@ -64,7 +64,7 @@ httpx==0.28.1
# via httpx-aiohttp
# via parallel-web
# via respx
-httpx-aiohttp==0.1.9
+httpx-aiohttp==0.1.12
# via parallel-web
humanize==4.13.0
# via nox
@@ -72,7 +72,7 @@ idna==3.11
# via anyio
# via httpx
# via yarl
-importlib-metadata==8.7.0
+importlib-metadata==8.7.1
iniconfig==2.1.0
# via pytest
markdown-it-py==3.0.0
@@ -85,14 +85,14 @@ multidict==6.7.0
mypy==1.17.0
mypy-extensions==1.1.0
# via mypy
-nodeenv==1.9.1
+nodeenv==1.10.0
# via pyright
nox==2025.11.12
packaging==25.0
# via dependency-groups
# via nox
# via pytest
-pathspec==0.12.1
+pathspec==1.0.3
# via mypy
platformdirs==4.4.0
# via virtualenv
@@ -118,13 +118,13 @@ python-dateutil==2.9.0.post0
# via time-machine
respx==0.22.0
rich==14.2.0
-ruff==0.14.7
+ruff==0.14.13
six==1.17.0
# via python-dateutil
sniffio==1.3.1
# via parallel-web
time-machine==2.19.0
-tomli==2.3.0
+tomli==2.4.0
# via dependency-groups
# via mypy
# via nox
@@ -144,7 +144,7 @@ typing-extensions==4.15.0
# via virtualenv
typing-inspection==0.4.2
# via pydantic
-virtualenv==20.35.4
+virtualenv==20.36.1
# via nox
yarl==1.22.0
# via aiohttp
diff --git a/requirements.lock b/requirements.lock
index 12e9e32..ff321d5 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -12,21 +12,21 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
-aiohttp==3.13.2
+aiohttp==3.13.3
# via httpx-aiohttp
# via parallel-web
aiosignal==1.4.0
# via aiohttp
annotated-types==0.7.0
# via pydantic
-anyio==4.12.0
+anyio==4.12.1
# via httpx
# via parallel-web
async-timeout==5.0.1
# via aiohttp
attrs==25.4.0
# via aiohttp
-certifi==2025.11.12
+certifi==2026.1.4
# via httpcore
# via httpx
distro==1.9.0
@@ -43,7 +43,7 @@ httpcore==1.0.9
httpx==0.28.1
# via httpx-aiohttp
# via parallel-web
-httpx-aiohttp==0.1.9
+httpx-aiohttp==0.1.12
# via parallel-web
idna==3.11
# via anyio
diff --git a/src/parallel/_base_client.py b/src/parallel/_base_client.py
index 3f29df7..5128667 100644
--- a/src/parallel/_base_client.py
+++ b/src/parallel/_base_client.py
@@ -86,6 +86,7 @@
APIConnectionError,
APIResponseValidationError,
)
+from ._utils._json import openapi_dumps
log: logging.Logger = logging.getLogger(__name__)
@@ -554,8 +555,10 @@ def _build_request(
kwargs["content"] = options.content
elif isinstance(json_data, bytes):
kwargs["content"] = json_data
- else:
- kwargs["json"] = json_data if is_given(json_data) else None
+ elif not files:
+ # Don't set content when JSON is sent as multipart/form-data,
+ # since httpx's content param overrides other body arguments
+ kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
diff --git a/src/parallel/_compat.py b/src/parallel/_compat.py
index 73a1f3e..020ffeb 100644
--- a/src/parallel/_compat.py
+++ b/src/parallel/_compat.py
@@ -139,6 +139,7 @@ def model_dump(
exclude_defaults: bool = False,
warnings: bool = True,
mode: Literal["json", "python"] = "python",
+ by_alias: bool | None = None,
) -> dict[str, Any]:
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
return model.model_dump(
@@ -148,13 +149,12 @@ def model_dump(
exclude_defaults=exclude_defaults,
# warnings are not supported in Pydantic v1
warnings=True if PYDANTIC_V1 else warnings,
+ by_alias=by_alias,
)
return cast(
"dict[str, Any]",
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
- exclude=exclude,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
+ exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
),
)
diff --git a/src/parallel/_response.py b/src/parallel/_response.py
index e24d45a..cf9f5f3 100644
--- a/src/parallel/_response.py
+++ b/src/parallel/_response.py
@@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
),
response=self.http_response,
client=cast(Any, self._client),
+ options=self._options,
),
)
@@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
cast_to=extract_stream_chunk_type(self._stream_cls),
response=self.http_response,
client=cast(Any, self._client),
+ options=self._options,
),
)
@@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
cast_to=cast_to,
response=self.http_response,
client=cast(Any, self._client),
+ options=self._options,
),
)
diff --git a/src/parallel/_streaming.py b/src/parallel/_streaming.py
index e65969e..8550546 100644
--- a/src/parallel/_streaming.py
+++ b/src/parallel/_streaming.py
@@ -4,7 +4,7 @@
import json
import inspect
from types import TracebackType
-from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast
+from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast
from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable
import httpx
@@ -13,6 +13,7 @@
if TYPE_CHECKING:
from ._client import Parallel, AsyncParallel
+ from ._models import FinalRequestOptions
_T = TypeVar("_T")
@@ -22,7 +23,7 @@ class Stream(Generic[_T]):
"""Provides the core interface to iterate over a synchronous stream response."""
response: httpx.Response
-
+ _options: Optional[FinalRequestOptions] = None
_decoder: SSEBytesDecoder
def __init__(
@@ -31,10 +32,12 @@ def __init__(
cast_to: type[_T],
response: httpx.Response,
client: Parallel,
+ options: Optional[FinalRequestOptions] = None,
) -> None:
self.response = response
self._cast_to = cast_to
self._client = client
+ self._options = options
self._decoder = client._make_sse_decoder()
self._iterator = self.__stream__()
@@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]):
"""Provides the core interface to iterate over an asynchronous stream response."""
response: httpx.Response
-
+ _options: Optional[FinalRequestOptions] = None
_decoder: SSEDecoder | SSEBytesDecoder
def __init__(
@@ -94,10 +97,12 @@ def __init__(
cast_to: type[_T],
response: httpx.Response,
client: AsyncParallel,
+ options: Optional[FinalRequestOptions] = None,
) -> None:
self.response = response
self._cast_to = cast_to
self._client = client
+ self._options = options
self._decoder = client._make_sse_decoder()
self._iterator = self.__stream__()
diff --git a/src/parallel/_utils/_compat.py b/src/parallel/_utils/_compat.py
index dd70323..2c70b29 100644
--- a/src/parallel/_utils/_compat.py
+++ b/src/parallel/_utils/_compat.py
@@ -26,7 +26,7 @@ def is_union(tp: Optional[Type[Any]]) -> bool:
else:
import types
- return tp is Union or tp is types.UnionType
+ return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap]
def is_typeddict(tp: Type[Any]) -> bool:
diff --git a/src/parallel/_utils/_json.py b/src/parallel/_utils/_json.py
new file mode 100644
index 0000000..6058421
--- /dev/null
+++ b/src/parallel/_utils/_json.py
@@ -0,0 +1,35 @@
+import json
+from typing import Any
+from datetime import datetime
+from typing_extensions import override
+
+import pydantic
+
+from .._compat import model_dump
+
+
+def openapi_dumps(obj: Any) -> bytes:
+ """
+ Serialize an object to UTF-8 encoded JSON bytes.
+
+ Extends the standard json.dumps with support for additional types
+ commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
+ """
+ return json.dumps(
+ obj,
+ cls=_CustomEncoder,
+ # Uses the same defaults as httpx's JSON serialization
+ ensure_ascii=False,
+ separators=(",", ":"),
+ allow_nan=False,
+ ).encode()
+
+
+class _CustomEncoder(json.JSONEncoder):
+ @override
+ def default(self, o: Any) -> Any:
+ if isinstance(o, datetime):
+ return o.isoformat()
+ if isinstance(o, pydantic.BaseModel):
+ return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
+ return super().default(o)
diff --git a/src/parallel/_version.py b/src/parallel/_version.py
index 528ebb8..e3da38f 100644
--- a/src/parallel/_version.py
+++ b/src/parallel/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "parallel"
-__version__ = "0.4.1" # x-release-please-version
+__version__ = "0.5.0" # x-release-please-version
diff --git a/src/parallel/resources/beta/api.md b/src/parallel/resources/beta/api.md
new file mode 100644
index 0000000..0b56fe7
--- /dev/null
+++ b/src/parallel/resources/beta/api.md
@@ -0,0 +1,99 @@
+# Beta
+
+Types:
+
+```python
+from parallel.types.beta import (
+ ExcerptSettings,
+ ExtractError,
+ ExtractResponse,
+ ExtractResult,
+ FetchPolicy,
+ SearchResult,
+ UsageItem,
+ WebSearchResult,
+)
+```
+
+Methods:
+
+- client.beta.extract(\*\*params) -> ExtractResponse
+- client.beta.search(\*\*params) -> SearchResult
+
+## TaskRun
+
+Types:
+
+```python
+from parallel.types.beta import (
+ BetaRunInput,
+ BetaTaskRunResult,
+ ErrorEvent,
+ McpServer,
+ McpToolCall,
+ ParallelBeta,
+ TaskRunEvent,
+ Webhook,
+ TaskRunEventsResponse,
+)
+```
+
+Methods:
+
+- client.beta.task_run.create(\*\*params) -> TaskRun
+- client.beta.task_run.events(run_id) -> TaskRunEventsResponse
+- client.beta.task_run.result(run_id, \*\*params) -> BetaTaskRunResult
+
+## TaskGroup
+
+Types:
+
+```python
+from parallel.types.beta import (
+ TaskGroup,
+ TaskGroupRunResponse,
+ TaskGroupStatus,
+ TaskGroupEventsResponse,
+ TaskGroupGetRunsResponse,
+)
+```
+
+Methods:
+
+- client.beta.task_group.create(\*\*params) -> TaskGroup
+- client.beta.task_group.retrieve(task_group_id) -> TaskGroup
+- client.beta.task_group.add_runs(task_group_id, \*\*params) -> TaskGroupRunResponse
+- client.beta.task_group.events(task_group_id, \*\*params) -> TaskGroupEventsResponse
+- client.beta.task_group.get_runs(task_group_id, \*\*params) -> TaskGroupGetRunsResponse
+
+## FindAll
+
+Types:
+
+```python
+from parallel.types.beta import (
+ FindAllCandidateMatchStatusEvent,
+ FindAllEnrichInput,
+ FindAllExtendInput,
+ FindAllRun,
+ FindAllRunInput,
+ FindAllRunResult,
+ FindAllRunStatusEvent,
+ FindAllSchema,
+ FindAllSchemaUpdatedEvent,
+ IngestInput,
+ FindAllEventsResponse,
+)
+```
+
+Methods:
+
+- client.beta.findall.create(\*\*params) -> FindAllRun
+- client.beta.findall.retrieve(findall_id) -> FindAllRun
+- client.beta.findall.cancel(findall_id) -> object
+- client.beta.findall.enrich(findall_id, \*\*params) -> FindAllSchema
+- client.beta.findall.events(findall_id, \*\*params) -> FindAllEventsResponse
+- client.beta.findall.extend(findall_id, \*\*params) -> FindAllSchema
+- client.beta.findall.ingest(\*\*params) -> FindAllSchema
+- client.beta.findall.result(findall_id) -> FindAllRunResult
+- client.beta.findall.schema(findall_id) -> FindAllSchema
diff --git a/tests/api_resources/beta/test_findall.py b/tests/api_resources/beta/test_findall.py
index b4bb063..6ee829d 100644
--- a/tests/api_resources/beta/test_findall.py
+++ b/tests/api_resources/beta/test_findall.py
@@ -296,7 +296,7 @@ def test_path_params_enrich(self, client: Parallel) -> None:
},
)
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_method_events(self, client: Parallel) -> None:
findall_stream = client.beta.findall.events(
@@ -304,7 +304,7 @@ def test_method_events(self, client: Parallel) -> None:
)
findall_stream.response.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_method_events_with_all_params(self, client: Parallel) -> None:
findall_stream = client.beta.findall.events(
@@ -315,7 +315,7 @@ def test_method_events_with_all_params(self, client: Parallel) -> None:
)
findall_stream.response.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_raw_response_events(self, client: Parallel) -> None:
response = client.beta.findall.with_raw_response.events(
@@ -326,7 +326,7 @@ def test_raw_response_events(self, client: Parallel) -> None:
stream = response.parse()
stream.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_streaming_response_events(self, client: Parallel) -> None:
with client.beta.findall.with_streaming_response.events(
@@ -340,7 +340,7 @@ def test_streaming_response_events(self, client: Parallel) -> None:
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_path_params_events(self, client: Parallel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `findall_id` but received ''"):
@@ -811,7 +811,7 @@ async def test_path_params_enrich(self, async_client: AsyncParallel) -> None:
},
)
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_method_events(self, async_client: AsyncParallel) -> None:
findall_stream = await async_client.beta.findall.events(
@@ -819,7 +819,7 @@ async def test_method_events(self, async_client: AsyncParallel) -> None:
)
await findall_stream.response.aclose()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_method_events_with_all_params(self, async_client: AsyncParallel) -> None:
findall_stream = await async_client.beta.findall.events(
@@ -830,7 +830,7 @@ async def test_method_events_with_all_params(self, async_client: AsyncParallel)
)
await findall_stream.response.aclose()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_raw_response_events(self, async_client: AsyncParallel) -> None:
response = await async_client.beta.findall.with_raw_response.events(
@@ -841,7 +841,7 @@ async def test_raw_response_events(self, async_client: AsyncParallel) -> None:
stream = await response.parse()
await stream.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_streaming_response_events(self, async_client: AsyncParallel) -> None:
async with async_client.beta.findall.with_streaming_response.events(
@@ -855,7 +855,7 @@ async def test_streaming_response_events(self, async_client: AsyncParallel) -> N
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_path_params_events(self, async_client: AsyncParallel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `findall_id` but received ''"):
diff --git a/tests/api_resources/beta/test_task_group.py b/tests/api_resources/beta/test_task_group.py
index 321e47e..7bcad49 100644
--- a/tests/api_resources/beta/test_task_group.py
+++ b/tests/api_resources/beta/test_task_group.py
@@ -211,7 +211,7 @@ def test_path_params_add_runs(self, client: Parallel) -> None:
],
)
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_method_events(self, client: Parallel) -> None:
task_group_stream = client.beta.task_group.events(
@@ -219,7 +219,7 @@ def test_method_events(self, client: Parallel) -> None:
)
task_group_stream.response.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_method_events_with_all_params(self, client: Parallel) -> None:
task_group_stream = client.beta.task_group.events(
@@ -229,7 +229,7 @@ def test_method_events_with_all_params(self, client: Parallel) -> None:
)
task_group_stream.response.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_raw_response_events(self, client: Parallel) -> None:
response = client.beta.task_group.with_raw_response.events(
@@ -240,7 +240,7 @@ def test_raw_response_events(self, client: Parallel) -> None:
stream = response.parse()
stream.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_streaming_response_events(self, client: Parallel) -> None:
with client.beta.task_group.with_streaming_response.events(
@@ -254,7 +254,7 @@ def test_streaming_response_events(self, client: Parallel) -> None:
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_path_params_events(self, client: Parallel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_group_id` but received ''"):
@@ -262,7 +262,7 @@ def test_path_params_events(self, client: Parallel) -> None:
task_group_id="",
)
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_method_get_runs(self, client: Parallel) -> None:
task_group_stream = client.beta.task_group.get_runs(
@@ -270,7 +270,7 @@ def test_method_get_runs(self, client: Parallel) -> None:
)
task_group_stream.response.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_method_get_runs_with_all_params(self, client: Parallel) -> None:
task_group_stream = client.beta.task_group.get_runs(
@@ -282,7 +282,7 @@ def test_method_get_runs_with_all_params(self, client: Parallel) -> None:
)
task_group_stream.response.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_raw_response_get_runs(self, client: Parallel) -> None:
response = client.beta.task_group.with_raw_response.get_runs(
@@ -293,7 +293,7 @@ def test_raw_response_get_runs(self, client: Parallel) -> None:
stream = response.parse()
stream.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_streaming_response_get_runs(self, client: Parallel) -> None:
with client.beta.task_group.with_streaming_response.get_runs(
@@ -307,7 +307,7 @@ def test_streaming_response_get_runs(self, client: Parallel) -> None:
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_path_params_get_runs(self, client: Parallel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_group_id` but received ''"):
@@ -511,7 +511,7 @@ async def test_path_params_add_runs(self, async_client: AsyncParallel) -> None:
],
)
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_method_events(self, async_client: AsyncParallel) -> None:
task_group_stream = await async_client.beta.task_group.events(
@@ -519,7 +519,7 @@ async def test_method_events(self, async_client: AsyncParallel) -> None:
)
await task_group_stream.response.aclose()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_method_events_with_all_params(self, async_client: AsyncParallel) -> None:
task_group_stream = await async_client.beta.task_group.events(
@@ -529,7 +529,7 @@ async def test_method_events_with_all_params(self, async_client: AsyncParallel)
)
await task_group_stream.response.aclose()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_raw_response_events(self, async_client: AsyncParallel) -> None:
response = await async_client.beta.task_group.with_raw_response.events(
@@ -540,7 +540,7 @@ async def test_raw_response_events(self, async_client: AsyncParallel) -> None:
stream = await response.parse()
await stream.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_streaming_response_events(self, async_client: AsyncParallel) -> None:
async with async_client.beta.task_group.with_streaming_response.events(
@@ -554,7 +554,7 @@ async def test_streaming_response_events(self, async_client: AsyncParallel) -> N
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_path_params_events(self, async_client: AsyncParallel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_group_id` but received ''"):
@@ -562,7 +562,7 @@ async def test_path_params_events(self, async_client: AsyncParallel) -> None:
task_group_id="",
)
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_method_get_runs(self, async_client: AsyncParallel) -> None:
task_group_stream = await async_client.beta.task_group.get_runs(
@@ -570,7 +570,7 @@ async def test_method_get_runs(self, async_client: AsyncParallel) -> None:
)
await task_group_stream.response.aclose()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_method_get_runs_with_all_params(self, async_client: AsyncParallel) -> None:
task_group_stream = await async_client.beta.task_group.get_runs(
@@ -582,7 +582,7 @@ async def test_method_get_runs_with_all_params(self, async_client: AsyncParallel
)
await task_group_stream.response.aclose()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_raw_response_get_runs(self, async_client: AsyncParallel) -> None:
response = await async_client.beta.task_group.with_raw_response.get_runs(
@@ -593,7 +593,7 @@ async def test_raw_response_get_runs(self, async_client: AsyncParallel) -> None:
stream = await response.parse()
await stream.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_streaming_response_get_runs(self, async_client: AsyncParallel) -> None:
async with async_client.beta.task_group.with_streaming_response.get_runs(
@@ -607,7 +607,7 @@ async def test_streaming_response_get_runs(self, async_client: AsyncParallel) ->
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_path_params_get_runs(self, async_client: AsyncParallel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_group_id` but received ''"):
diff --git a/tests/api_resources/beta/test_task_run.py b/tests/api_resources/beta/test_task_run.py
index 6728cbd..c143e45 100644
--- a/tests/api_resources/beta/test_task_run.py
+++ b/tests/api_resources/beta/test_task_run.py
@@ -94,7 +94,7 @@ def test_streaming_response_create(self, client: Parallel) -> None:
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_method_events(self, client: Parallel) -> None:
task_run_stream = client.beta.task_run.events(
@@ -102,7 +102,7 @@ def test_method_events(self, client: Parallel) -> None:
)
task_run_stream.response.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_raw_response_events(self, client: Parallel) -> None:
response = client.beta.task_run.with_raw_response.events(
@@ -113,7 +113,7 @@ def test_raw_response_events(self, client: Parallel) -> None:
stream = response.parse()
stream.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_streaming_response_events(self, client: Parallel) -> None:
with client.beta.task_run.with_streaming_response.events(
@@ -127,7 +127,7 @@ def test_streaming_response_events(self, client: Parallel) -> None:
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
def test_path_params_events(self, client: Parallel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"):
@@ -263,7 +263,7 @@ async def test_streaming_response_create(self, async_client: AsyncParallel) -> N
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_method_events(self, async_client: AsyncParallel) -> None:
task_run_stream = await async_client.beta.task_run.events(
@@ -271,7 +271,7 @@ async def test_method_events(self, async_client: AsyncParallel) -> None:
)
await task_run_stream.response.aclose()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_raw_response_events(self, async_client: AsyncParallel) -> None:
response = await async_client.beta.task_run.with_raw_response.events(
@@ -282,7 +282,7 @@ async def test_raw_response_events(self, async_client: AsyncParallel) -> None:
stream = await response.parse()
await stream.close()
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_streaming_response_events(self, async_client: AsyncParallel) -> None:
async with async_client.beta.task_run.with_streaming_response.events(
@@ -296,7 +296,7 @@ async def test_streaming_response_events(self, async_client: AsyncParallel) -> N
assert cast(Any, response.is_closed) is True
- @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses")
+ @pytest.mark.skip(reason="Mock server doesn't support text/event-stream responses")
@parametrize
async def test_path_params_events(self, async_client: AsyncParallel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"):
diff --git a/tests/test_client.py b/tests/test_client.py
index feec74d..c2b772f 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -961,6 +961,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Test that the proxy environment variables are set correctly
monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+ # Delete in case our environment has any proxy env vars set
+ monkeypatch.delenv("HTTP_PROXY", raising=False)
+ monkeypatch.delenv("ALL_PROXY", raising=False)
+ monkeypatch.delenv("NO_PROXY", raising=False)
+ monkeypatch.delenv("http_proxy", raising=False)
+ monkeypatch.delenv("https_proxy", raising=False)
+ monkeypatch.delenv("all_proxy", raising=False)
+ monkeypatch.delenv("no_proxy", raising=False)
client = DefaultHttpxClient()
@@ -1881,6 +1889,14 @@ async def test_get_platform(self) -> None:
async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Test that the proxy environment variables are set correctly
monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+ # Delete in case our environment has any proxy env vars set
+ monkeypatch.delenv("HTTP_PROXY", raising=False)
+ monkeypatch.delenv("ALL_PROXY", raising=False)
+ monkeypatch.delenv("NO_PROXY", raising=False)
+ monkeypatch.delenv("http_proxy", raising=False)
+ monkeypatch.delenv("https_proxy", raising=False)
+ monkeypatch.delenv("all_proxy", raising=False)
+ monkeypatch.delenv("no_proxy", raising=False)
client = DefaultAsyncHttpxClient()
diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py
new file mode 100644
index 0000000..d6c0dfd
--- /dev/null
+++ b/tests/test_utils/test_json.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+import datetime
+from typing import Union
+
+import pydantic
+
+from parallel import _compat
+from parallel._utils._json import openapi_dumps
+
+
+class TestOpenapiDumps:
+ def test_basic(self) -> None:
+ data = {"key": "value", "number": 42}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"key":"value","number":42}'
+
+ def test_datetime_serialization(self) -> None:
+ dt = datetime.datetime(2023, 1, 1, 12, 0, 0)
+ data = {"datetime": dt}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}'
+
+ def test_pydantic_model_serialization(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str
+ last_name: str
+ age: int
+
+ model_instance = User(first_name="John", last_name="Kramer", age=83)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}'
+
+ def test_pydantic_model_with_default_values(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+ score: int = 0
+
+ model_instance = User(name="Alice")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Alice"}}'
+
+ def test_pydantic_model_with_default_values_overridden(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+
+ model_instance = User(name="Bob", role="admin", active=False)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}'
+
+ def test_pydantic_model_with_alias(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str = pydantic.Field(alias="firstName")
+ last_name: str = pydantic.Field(alias="lastName")
+
+ model_instance = User(firstName="John", lastName="Doe")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}'
+
+ def test_pydantic_model_with_alias_and_default(self) -> None:
+ class User(pydantic.BaseModel):
+ user_name: str = pydantic.Field(alias="userName")
+ user_role: str = pydantic.Field(default="member", alias="userRole")
+ is_active: bool = pydantic.Field(default=True, alias="isActive")
+
+ model_instance = User(userName="charlie")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"charlie"}}'
+
+ model_with_overrides = User(userName="diana", userRole="admin", isActive=False)
+ data = {"model": model_with_overrides}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}'
+
+ def test_pydantic_model_with_nested_models_and_defaults(self) -> None:
+ class Address(pydantic.BaseModel):
+ street: str
+ city: str = "Unknown"
+
+ class User(pydantic.BaseModel):
+ name: str
+ address: Address
+ verified: bool = False
+
+ if _compat.PYDANTIC_V1:
+ # to handle forward references in Pydantic v1
+ User.update_forward_refs(**locals()) # type: ignore[reportDeprecated]
+
+ address = Address(street="123 Main St")
+ user = User(name="Diana", address=address)
+ data = {"user": user}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}'
+
+ address_with_city = Address(street="456 Oak Ave", city="Boston")
+ user_verified = User(name="Eve", address=address_with_city, verified=True)
+ data = {"user": user_verified}
+ json_bytes = openapi_dumps(data)
+ assert (
+ json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}'
+ )
+
+ def test_pydantic_model_with_optional_fields(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ email: Union[str, None]
+ phone: Union[str, None]
+
+ model_with_none = User(name="Eve", email=None, phone=None)
+ data = {"model": model_with_none}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}'
+
+ model_with_values = User(name="Frank", email="frank@example.com", phone=None)
+ data = {"model": model_with_values}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}'