Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 27 additions & 57 deletions src/courier/resources/providers/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Dict, Optional
from typing import Dict, Iterable

import httpx

Expand Down Expand Up @@ -317,10 +317,7 @@ def patch(
self,
id: str,
*,
alias: Optional[str] | Omit = omit,
provider: str | Omit = omit,
settings: Dict[str, object] | Omit = omit,
title: str | Omit = omit,
patch: Iterable[provider_patch_params.Patch],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
Expand All @@ -329,23 +326,19 @@ def patch(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Provider:
"""
Updates part of a provider's configuration and keeps every field you omit.
Settings merge into the stored settings key by key, and a setting sent as null
is removed.
Applies JSON Patch (RFC 6902) operations to a provider configuration and keeps
every field the operations don't touch. Paths address the provider as it is
returned: /title, /alias and /settings/<key>. The operations apply in order, and
if one fails, nothing is saved.

Args:
alias: Updated alias. Set to null to clear it.

provider: The provider key identifying the type. Optional. When sent, it must match the
stored provider, otherwise the request returns a 400.

settings: Provider-specific settings (snake_case keys) to merge into the stored settings,
using JSON Merge Patch (RFC 7396). Each key you send replaces the stored value,
a key set to null is removed, nested objects merge recursively, and arrays
replace the stored value. Keys you omit keep their stored values. Set
`rate_limit` to null to remove this configuration's send rate limit.

title: Updated display title.
patch:
Operations to apply, in order. Paths address the provider as it is returned:
`/title`, `/alias`, `/provider`, and `/settings/<key>` with the snake_case
setting keys, including nested paths such as `/settings/rate_limit/rate`. If any
operation fails, for example a `test`, nothing is saved and the request returns
a 400. The result can't change `provider`, remove `title`, or add other
top-level fields. Removing `/alias` clears it.

extra_headers: Send extra headers

Expand All @@ -359,15 +352,7 @@ def patch(
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._patch(
path_template("/providers/{id}", id=id),
body=maybe_transform(
{
"alias": alias,
"provider": provider,
"settings": settings,
"title": title,
},
provider_patch_params.ProviderPatchParams,
),
body=maybe_transform({"patch": patch}, provider_patch_params.ProviderPatchParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down Expand Up @@ -660,10 +645,7 @@ async def patch(
self,
id: str,
*,
alias: Optional[str] | Omit = omit,
provider: str | Omit = omit,
settings: Dict[str, object] | Omit = omit,
title: str | Omit = omit,
patch: Iterable[provider_patch_params.Patch],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
Expand All @@ -672,23 +654,19 @@ async def patch(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Provider:
"""
Updates part of a provider's configuration and keeps every field you omit.
Settings merge into the stored settings key by key, and a setting sent as null
is removed.
Applies JSON Patch (RFC 6902) operations to a provider configuration and keeps
every field the operations don't touch. Paths address the provider as it is
returned: /title, /alias and /settings/<key>. The operations apply in order, and
if one fails, nothing is saved.

Args:
alias: Updated alias. Set to null to clear it.

provider: The provider key identifying the type. Optional. When sent, it must match the
stored provider, otherwise the request returns a 400.

settings: Provider-specific settings (snake_case keys) to merge into the stored settings,
using JSON Merge Patch (RFC 7396). Each key you send replaces the stored value,
a key set to null is removed, nested objects merge recursively, and arrays
replace the stored value. Keys you omit keep their stored values. Set
`rate_limit` to null to remove this configuration's send rate limit.

title: Updated display title.
patch:
Operations to apply, in order. Paths address the provider as it is returned:
`/title`, `/alias`, `/provider`, and `/settings/<key>` with the snake_case
setting keys, including nested paths such as `/settings/rate_limit/rate`. If any
operation fails, for example a `test`, nothing is saved and the request returns
a 400. The result can't change `provider`, remove `title`, or add other
top-level fields. Removing `/alias` clears it.

extra_headers: Send extra headers

Expand All @@ -702,15 +680,7 @@ async def patch(
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._patch(
path_template("/providers/{id}", id=id),
body=await async_maybe_transform(
{
"alias": alias,
"provider": provider,
"settings": settings,
"title": title,
},
provider_patch_params.ProviderPatchParams,
),
body=await async_maybe_transform({"patch": patch}, provider_patch_params.ProviderPatchParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down
51 changes: 31 additions & 20 deletions src/courier/types/provider_patch_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,42 @@

from __future__ import annotations

from typing import Dict, Optional
from typing_extensions import TypedDict
from typing import Iterable
from typing_extensions import Literal, Required, TypedDict

__all__ = ["ProviderPatchParams"]
__all__ = ["ProviderPatchParams", "Patch"]


class ProviderPatchParams(TypedDict, total=False):
alias: Optional[str]
"""Updated alias. Set to null to clear it."""
patch: Required[Iterable[Patch]]
"""Operations to apply, in order.

Paths address the provider as it is returned: `/title`, `/alias`, `/provider`,
and `/settings/<key>` with the snake_case setting keys, including nested paths
such as `/settings/rate_limit/rate`. If any operation fails, for example a
`test`, nothing is saved and the request returns a 400. The result can't change
`provider`, remove `title`, or add other top-level fields. Removing `/alias`
clears it.
"""

provider: str
"""The provider key identifying the type.

Optional. When sent, it must match the stored provider, otherwise the request
returns a 400.
"""
_PatchReservedKeywords = TypedDict(
"_PatchReservedKeywords",
{
"from": str,
},
total=False,
)

settings: Dict[str, object]
"""
Provider-specific settings (snake_case keys) to merge into the stored settings,
using JSON Merge Patch (RFC 7396). Each key you send replaces the stored value,
a key set to null is removed, nested objects merge recursively, and arrays
replace the stored value. Keys you omit keep their stored values. Set
`rate_limit` to null to remove this configuration's send rate limit.
"""

title: str
"""Updated display title."""
class Patch(_PatchReservedKeywords, total=False):
"""One JSON Patch (RFC 6902) operation."""

op: Required[Literal["add", "remove", "replace", "move", "copy", "test"]]
"""The operation to perform."""

path: Required[str]
"""JSON Pointer to the field to operate on, such as `/settings/api_key`."""

value: object
"""The value for `add`, `replace` and `test`. Any JSON value."""
104 changes: 80 additions & 24 deletions tests/api_resources/test_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,18 +250,16 @@ def test_path_params_delete(self, client: Courier) -> None:
def test_method_patch(self, client: Courier) -> None:
provider = client.providers.patch(
id="id",
)
assert_matches_type(Provider, provider, path=["response"])

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_patch_with_all_params(self, client: Courier) -> None:
provider = client.providers.patch(
id="id",
alias="alias",
provider="provider",
settings={"api_key": "bar"},
title="Production SendGrid",
patch=[
{
"op": "replace",
"path": "/title",
},
{
"op": "replace",
"path": "/settings/api_key",
},
],
)
assert_matches_type(Provider, provider, path=["response"])

Expand All @@ -270,6 +268,16 @@ def test_method_patch_with_all_params(self, client: Courier) -> None:
def test_raw_response_patch(self, client: Courier) -> None:
response = client.providers.with_raw_response.patch(
id="id",
patch=[
{
"op": "replace",
"path": "/title",
},
{
"op": "replace",
"path": "/settings/api_key",
},
],
)

assert response.is_closed is True
Expand All @@ -282,6 +290,16 @@ def test_raw_response_patch(self, client: Courier) -> None:
def test_streaming_response_patch(self, client: Courier) -> None:
with client.providers.with_streaming_response.patch(
id="id",
patch=[
{
"op": "replace",
"path": "/title",
},
{
"op": "replace",
"path": "/settings/api_key",
},
],
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
Expand All @@ -297,6 +315,16 @@ def test_path_params_patch(self, client: Courier) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
client.providers.with_raw_response.patch(
id="",
patch=[
{
"op": "replace",
"path": "/title",
},
{
"op": "replace",
"path": "/settings/api_key",
},
],
)


Expand Down Expand Up @@ -535,18 +563,16 @@ async def test_path_params_delete(self, async_client: AsyncCourier) -> None:
async def test_method_patch(self, async_client: AsyncCourier) -> None:
provider = await async_client.providers.patch(
id="id",
)
assert_matches_type(Provider, provider, path=["response"])

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_patch_with_all_params(self, async_client: AsyncCourier) -> None:
provider = await async_client.providers.patch(
id="id",
alias="alias",
provider="provider",
settings={"api_key": "bar"},
title="Production SendGrid",
patch=[
{
"op": "replace",
"path": "/title",
},
{
"op": "replace",
"path": "/settings/api_key",
},
],
)
assert_matches_type(Provider, provider, path=["response"])

Expand All @@ -555,6 +581,16 @@ async def test_method_patch_with_all_params(self, async_client: AsyncCourier) ->
async def test_raw_response_patch(self, async_client: AsyncCourier) -> None:
response = await async_client.providers.with_raw_response.patch(
id="id",
patch=[
{
"op": "replace",
"path": "/title",
},
{
"op": "replace",
"path": "/settings/api_key",
},
],
)

assert response.is_closed is True
Expand All @@ -567,6 +603,16 @@ async def test_raw_response_patch(self, async_client: AsyncCourier) -> None:
async def test_streaming_response_patch(self, async_client: AsyncCourier) -> None:
async with async_client.providers.with_streaming_response.patch(
id="id",
patch=[
{
"op": "replace",
"path": "/title",
},
{
"op": "replace",
"path": "/settings/api_key",
},
],
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
Expand All @@ -582,4 +628,14 @@ async def test_path_params_patch(self, async_client: AsyncCourier) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
await async_client.providers.with_raw_response.patch(
id="",
patch=[
{
"op": "replace",
"path": "/title",
},
{
"op": "replace",
"path": "/settings/api_key",
},
],
)
Loading