diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 880a183..ba205d6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.4.1-beta.0" + ".": "0.4.1-beta.1" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 8846e78..e35fdaa 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/miru-ml%2Fmiru-server-7d0dd143c8ea9bc230d9810d8cfb3fde8b1d4eb48295ebf2db7ea67916fb96c3.yml -openapi_spec_hash: b6667a2e80a356a67bab83f8d5f6e51a -config_hash: 78c50efd3bbfb4969846973cb0609eff +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/miru-ml%2Fmiru-server-1187ba20c7bd5d91919c6e65f71201c9997541aae5ea6b3068b2e8094b4b8d0c.yml +openapi_spec_hash: fd892348c3cd6e7ec5bdce4d8134bf14 +config_hash: c47912de5d58c4db1a9ed9e3f82a24a0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f20356..6779dd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.4.1-beta.1 (2025-10-05) + +Full Changelog: [v0.4.1-beta.0...v0.4.1-beta.1](https://github.com/miruml/python-server-sdk/compare/v0.4.1-beta.0...v0.4.1-beta.1) + +### Features + +* **api:** manual updates ([1f24c3f](https://github.com/miruml/python-server-sdk/commit/1f24c3f238e13344e158a909a8444fe6a02534ec)) +* **api:** manual updates ([616466d](https://github.com/miruml/python-server-sdk/commit/616466d196871cb65ad48ecccf292be3596043b1)) + ## 0.4.1-beta.0 (2025-10-05) Full Changelog: [v0.4.0...v0.4.1-beta.0](https://github.com/miruml/python-server-sdk/compare/v0.4.0...v0.4.1-beta.0) diff --git a/README.md b/README.md index 4f8a048..5e021fd 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It is generated with [Stainless](https://www.stainless.com/). ## Documentation -The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [docs.miruml.com](https://docs.miruml.com). The full API of this library can be found in [api.md](api.md). ## Installation @@ -30,6 +30,8 @@ from miru_server_sdk import Miru client = Miru( api_key=os.environ.get("MIRU_SERVER_API_KEY"), # This is the default and can be omitted + # or 'production' | 'local'; defaults to "production". + environment="staging", ) config_instance = client.config_instances.retrieve( @@ -54,6 +56,8 @@ from miru_server_sdk import AsyncMiru client = AsyncMiru( api_key=os.environ.get("MIRU_SERVER_API_KEY"), # This is the default and can be omitted + # or 'production' | 'local'; defaults to "production". + environment="staging", ) diff --git a/SECURITY.md b/SECURITY.md index 4d9ec81..f5f749d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -18,6 +18,10 @@ before making any information public. If you encounter security issues that are not directly related to SDKs but pertain to the services or products provided by Miru, please follow the respective company's security reporting guidelines. +### Miru Terms and Policies + +Please contact ben@miruml.com for any questions or concerns regarding the security of our services. + --- Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/api.md b/api.md index 84bec5c..31eb8c5 100644 --- a/api.md +++ b/api.md @@ -67,3 +67,11 @@ Methods: - client.releases.retrieve(release_id, \*\*params) -> Release - client.releases.list(\*\*params) -> ReleaseListResponse + +# Webhooks + +Types: + +```python +from miru_server_sdk.types import DeploymentValidateWebhookEvent, UnwrapWebhookEvent +``` diff --git a/pyproject.toml b/pyproject.toml index ccf86cb..35e6b96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "miru_server_sdk" -version = "0.4.1-beta.0" +version = "0.4.1-beta.1" description = "The official Python library for the miru API" dynamic = ["readme"] license = "MIT" authors = [ -{ name = "Miru", email = "" }, +{ name = "Miru", email = "ben@miruml.com" }, ] dependencies = [ "httpx>=0.23.0, <1", diff --git a/src/miru_server_sdk/__init__.py b/src/miru_server_sdk/__init__.py index 55231d3..ec875e0 100644 --- a/src/miru_server_sdk/__init__.py +++ b/src/miru_server_sdk/__init__.py @@ -5,7 +5,18 @@ from . import types from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path -from ._client import Miru, Client, Stream, Timeout, AsyncMiru, Transport, AsyncClient, AsyncStream, RequestOptions +from ._client import ( + ENVIRONMENTS, + Miru, + Client, + Stream, + Timeout, + AsyncMiru, + Transport, + AsyncClient, + AsyncStream, + RequestOptions, +) from ._models import BaseModel from ._version import __title__, __version__ from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse @@ -63,6 +74,7 @@ "AsyncStream", "Miru", "AsyncMiru", + "ENVIRONMENTS", "file_from_path", "BaseModel", "DEFAULT_TIMEOUT", diff --git a/src/miru_server_sdk/_client.py b/src/miru_server_sdk/_client.py index 2302a6b..c5af8a2 100644 --- a/src/miru_server_sdk/_client.py +++ b/src/miru_server_sdk/_client.py @@ -3,8 +3,8 @@ from __future__ import annotations import os -from typing import Any, Mapping -from typing_extensions import Self, override +from typing import Any, Dict, Mapping, cast +from typing_extensions import Self, Literal, override import httpx @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import devices, releases, deployments, config_instances +from .resources import devices, releases, webhooks, deployments, config_instances from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import MiruError, APIStatusError from ._base_client import ( @@ -30,7 +30,23 @@ AsyncAPIClient, ) -__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Miru", "AsyncMiru", "Client", "AsyncClient"] +__all__ = [ + "ENVIRONMENTS", + "Timeout", + "Transport", + "ProxiesTypes", + "RequestOptions", + "Miru", + "AsyncMiru", + "Client", + "AsyncClient", +] + +ENVIRONMENTS: Dict[str, str] = { + "production": "https://configs.api.miruml.com/v1", + "staging": "https://configs.dev.api.miruml.com/v1", + "local": "http://localhost:8080/v1", +} class Miru(SyncAPIClient): @@ -38,6 +54,7 @@ class Miru(SyncAPIClient): deployments: deployments.DeploymentsResource devices: devices.DevicesResource releases: releases.ReleasesResource + webhooks: webhooks.WebhooksResource with_raw_response: MiruWithRawResponse with_streaming_response: MiruWithStreamedResponse @@ -46,13 +63,16 @@ class Miru(SyncAPIClient): host: str version: str + _environment: Literal["production", "staging", "local"] | NotGiven + def __init__( self, *, api_key: str | None = None, host: str | None = None, version: str | None = None, - base_url: str | httpx.URL | None = None, + environment: Literal["production", "staging", "local"] | NotGiven = not_given, + base_url: str | httpx.URL | None | NotGiven = not_given, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, @@ -94,10 +114,31 @@ def __init__( version = os.environ.get("MIRU_SERVER_VERSION") or "v1" self.version = version - if base_url is None: - base_url = os.environ.get("MIRU_BASE_URL") - if base_url is None: - base_url = f"https://{host}/{version}" + self._environment = environment + + base_url_env = os.environ.get("MIRU_BASE_URL") + if is_given(base_url) and base_url is not None: + # cast required because mypy doesn't understand the type narrowing + base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] + elif is_given(environment): + if base_url_env and base_url is not None: + raise ValueError( + "Ambiguous URL; The `MIRU_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + ) + + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc + elif base_url_env is not None: + base_url = base_url_env + else: + self._environment = environment = "production" + + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc super().__init__( version=__version__, @@ -114,6 +155,7 @@ def __init__( self.deployments = deployments.DeploymentsResource(self) self.devices = devices.DevicesResource(self) self.releases = releases.ReleasesResource(self) + self.webhooks = webhooks.WebhooksResource(self) self.with_raw_response = MiruWithRawResponse(self) self.with_streaming_response = MiruWithStreamedResponse(self) @@ -143,6 +185,7 @@ def copy( api_key: str | None = None, host: str | None = None, version: str | None = None, + environment: Literal["production", "staging", "local"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, @@ -180,6 +223,7 @@ def copy( host=host or self.host, version=version or self.version, base_url=base_url or self.base_url, + environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, @@ -231,6 +275,7 @@ class AsyncMiru(AsyncAPIClient): deployments: deployments.AsyncDeploymentsResource devices: devices.AsyncDevicesResource releases: releases.AsyncReleasesResource + webhooks: webhooks.AsyncWebhooksResource with_raw_response: AsyncMiruWithRawResponse with_streaming_response: AsyncMiruWithStreamedResponse @@ -239,13 +284,16 @@ class AsyncMiru(AsyncAPIClient): host: str version: str + _environment: Literal["production", "staging", "local"] | NotGiven + def __init__( self, *, api_key: str | None = None, host: str | None = None, version: str | None = None, - base_url: str | httpx.URL | None = None, + environment: Literal["production", "staging", "local"] | NotGiven = not_given, + base_url: str | httpx.URL | None | NotGiven = not_given, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, @@ -287,10 +335,31 @@ def __init__( version = os.environ.get("MIRU_SERVER_VERSION") or "v1" self.version = version - if base_url is None: - base_url = os.environ.get("MIRU_BASE_URL") - if base_url is None: - base_url = f"https://{host}/{version}" + self._environment = environment + + base_url_env = os.environ.get("MIRU_BASE_URL") + if is_given(base_url) and base_url is not None: + # cast required because mypy doesn't understand the type narrowing + base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] + elif is_given(environment): + if base_url_env and base_url is not None: + raise ValueError( + "Ambiguous URL; The `MIRU_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + ) + + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc + elif base_url_env is not None: + base_url = base_url_env + else: + self._environment = environment = "production" + + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc super().__init__( version=__version__, @@ -307,6 +376,7 @@ def __init__( self.deployments = deployments.AsyncDeploymentsResource(self) self.devices = devices.AsyncDevicesResource(self) self.releases = releases.AsyncReleasesResource(self) + self.webhooks = webhooks.AsyncWebhooksResource(self) self.with_raw_response = AsyncMiruWithRawResponse(self) self.with_streaming_response = AsyncMiruWithStreamedResponse(self) @@ -336,6 +406,7 @@ def copy( api_key: str | None = None, host: str | None = None, version: str | None = None, + environment: Literal["production", "staging", "local"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, @@ -373,6 +444,7 @@ def copy( host=host or self.host, version=version or self.version, base_url=base_url or self.base_url, + environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, diff --git a/src/miru_server_sdk/_version.py b/src/miru_server_sdk/_version.py index 11b2276..173fc4f 100644 --- a/src/miru_server_sdk/_version.py +++ b/src/miru_server_sdk/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "miru_server_sdk" -__version__ = "0.4.1-beta.0" # x-release-please-version +__version__ = "0.4.1-beta.1" # x-release-please-version diff --git a/src/miru_server_sdk/resources/__init__.py b/src/miru_server_sdk/resources/__init__.py index 20b1dcc..54e1eee 100644 --- a/src/miru_server_sdk/resources/__init__.py +++ b/src/miru_server_sdk/resources/__init__.py @@ -16,6 +16,7 @@ ReleasesResourceWithStreamingResponse, AsyncReleasesResourceWithStreamingResponse, ) +from .webhooks import WebhooksResource, AsyncWebhooksResource from .deployments import ( DeploymentsResource, AsyncDeploymentsResource, @@ -58,4 +59,6 @@ "AsyncReleasesResourceWithRawResponse", "ReleasesResourceWithStreamingResponse", "AsyncReleasesResourceWithStreamingResponse", + "WebhooksResource", + "AsyncWebhooksResource", ] diff --git a/src/miru_server_sdk/resources/deployments.py b/src/miru_server_sdk/resources/deployments.py index ca7be86..3aaa3c6 100644 --- a/src/miru_server_sdk/resources/deployments.py +++ b/src/miru_server_sdk/resources/deployments.py @@ -79,10 +79,10 @@ def create( new_config_instances: The _new_ config instances to create for this deployment. A deployment must have exactly one config instance for each config schema in the deployment's release. If less config instances are provided than the number of schemas, the deployment - will 'transfer' config instances its patch source. Archived config instances (in - patch sources) cannot be transferred, they must be created anew. + will 'transfer' config instances from the deployment it is patched from. + Archived config instances cannot be transferred. - release_id: The ID of the release which this deployment adheres to. + release_id: The release ID which this deployment adheres to. target_status: Desired state of the deployment. @@ -94,15 +94,11 @@ def create( release is the device's current release. If custom validation is enabled for the release, the deployment must pass - validation before fulfilling the target status. Otherwise, the deployment fails - and current deployments are unaffected. + validation before fulfilling the target status. expand: The fields to expand in the deployment. - patch_source_id: The ID of the deployment that this deployment was (optionally) patched from. If - no patch source is provided, the deployment is considered to be 'new'-- it is - not a delta from an existing deployment. If a patch source is provided, the - deployment is considered to be a delta of changes from the patch source. + patch_source_id: The ID of the deployment that this deployment was patched from. extra_headers: Send extra headers @@ -275,8 +271,7 @@ def validate( config_instances: The config instance errors for this deployment. is_valid: Whether the deployment is valid. If invalid, the deployment is immediately - archived and marked as 'failed'. If valid, the deployment continues to its - desired target status (i.e. 'pending', 'approved', or 'deployed'). + archived and marked as 'failed'. message: A message displayed on the deployment level in the UI. @@ -355,10 +350,10 @@ async def create( new_config_instances: The _new_ config instances to create for this deployment. A deployment must have exactly one config instance for each config schema in the deployment's release. If less config instances are provided than the number of schemas, the deployment - will 'transfer' config instances its patch source. Archived config instances (in - patch sources) cannot be transferred, they must be created anew. + will 'transfer' config instances from the deployment it is patched from. + Archived config instances cannot be transferred. - release_id: The ID of the release which this deployment adheres to. + release_id: The release ID which this deployment adheres to. target_status: Desired state of the deployment. @@ -370,15 +365,11 @@ async def create( release is the device's current release. If custom validation is enabled for the release, the deployment must pass - validation before fulfilling the target status. Otherwise, the deployment fails - and current deployments are unaffected. + validation before fulfilling the target status. expand: The fields to expand in the deployment. - patch_source_id: The ID of the deployment that this deployment was (optionally) patched from. If - no patch source is provided, the deployment is considered to be 'new'-- it is - not a delta from an existing deployment. If a patch source is provided, the - deployment is considered to be a delta of changes from the patch source. + patch_source_id: The ID of the deployment that this deployment was patched from. extra_headers: Send extra headers @@ -553,8 +544,7 @@ async def validate( config_instances: The config instance errors for this deployment. is_valid: Whether the deployment is valid. If invalid, the deployment is immediately - archived and marked as 'failed'. If valid, the deployment continues to its - desired target status (i.e. 'pending', 'approved', or 'deployed'). + archived and marked as 'failed'. message: A message displayed on the deployment level in the UI. diff --git a/src/miru_server_sdk/resources/webhooks.py b/src/miru_server_sdk/resources/webhooks.py new file mode 100644 index 0000000..7531355 --- /dev/null +++ b/src/miru_server_sdk/resources/webhooks.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import json +from typing import cast + +from .._models import construct_type +from .._resource import SyncAPIResource, AsyncAPIResource +from ..types.unwrap_webhook_event import UnwrapWebhookEvent + +__all__ = ["WebhooksResource", "AsyncWebhooksResource"] + + +class WebhooksResource(SyncAPIResource): + def unwrap(self, payload: str) -> UnwrapWebhookEvent: + return cast( + UnwrapWebhookEvent, + construct_type( + type_=UnwrapWebhookEvent, + value=json.loads(payload), + ), + ) + + +class AsyncWebhooksResource(AsyncAPIResource): + def unwrap(self, payload: str) -> UnwrapWebhookEvent: + return cast( + UnwrapWebhookEvent, + construct_type( + type_=UnwrapWebhookEvent, + value=json.loads(payload), + ), + ) diff --git a/src/miru_server_sdk/types/__init__.py b/src/miru_server_sdk/types/__init__.py index b98e167..3bcdf2d 100644 --- a/src/miru_server_sdk/types/__init__.py +++ b/src/miru_server_sdk/types/__init__.py @@ -17,6 +17,7 @@ from .device_create_params import DeviceCreateParams as DeviceCreateParams from .device_list_response import DeviceListResponse as DeviceListResponse from .device_update_params import DeviceUpdateParams as DeviceUpdateParams +from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .release_list_response import ReleaseListResponse as ReleaseListResponse from .deployment_list_params import DeploymentListParams as DeploymentListParams from .device_delete_response import DeviceDeleteResponse as DeviceDeleteResponse @@ -29,6 +30,7 @@ from .deployment_validate_response import DeploymentValidateResponse as DeploymentValidateResponse from .config_instance_list_response import ConfigInstanceListResponse as ConfigInstanceListResponse from .config_instance_retrieve_params import ConfigInstanceRetrieveParams as ConfigInstanceRetrieveParams +from .deployment_validate_webhook_event import DeploymentValidateWebhookEvent as DeploymentValidateWebhookEvent from .device_create_activation_token_params import ( DeviceCreateActivationTokenParams as DeviceCreateActivationTokenParams, ) diff --git a/src/miru_server_sdk/types/deployment_create_params.py b/src/miru_server_sdk/types/deployment_create_params.py index 085da48..14d9de8 100644 --- a/src/miru_server_sdk/types/deployment_create_params.py +++ b/src/miru_server_sdk/types/deployment_create_params.py @@ -20,13 +20,12 @@ class DeploymentCreateParams(TypedDict, total=False): A deployment must have exactly one config instance for each config schema in the deployment's release. If less config instances are provided than the number of - schemas, the deployment will 'transfer' config instances its patch source. - Archived config instances (in patch sources) cannot be transferred, they must be - created anew. + schemas, the deployment will 'transfer' config instances from the deployment it + is patched from. Archived config instances cannot be transferred. """ release_id: Required[str] - """The ID of the release which this deployment adheres to.""" + """The release ID which this deployment adheres to.""" target_status: Required[Literal["pending", "approved", "deployed"]] """Desired state of the deployment. @@ -39,20 +38,14 @@ class DeploymentCreateParams(TypedDict, total=False): release is the device's current release. If custom validation is enabled for the release, the deployment must pass - validation before fulfilling the target status. Otherwise, the deployment fails - and current deployments are unaffected. + validation before fulfilling the target status. """ expand: List[Literal["device", "release", "config_instances"]] """The fields to expand in the deployment.""" patch_source_id: str - """The ID of the deployment that this deployment was (optionally) patched from. - - If no patch source is provided, the deployment is considered to be 'new'-- it is - not a delta from an existing deployment. If a patch source is provided, the - deployment is considered to be a delta of changes from the patch source. - """ + """The ID of the deployment that this deployment was patched from.""" class NewConfigInstance(TypedDict, total=False): diff --git a/src/miru_server_sdk/types/deployment_validate_params.py b/src/miru_server_sdk/types/deployment_validate_params.py index 1a4e7d6..fa95a39 100644 --- a/src/miru_server_sdk/types/deployment_validate_params.py +++ b/src/miru_server_sdk/types/deployment_validate_params.py @@ -17,9 +17,7 @@ class DeploymentValidateParams(TypedDict, total=False): is_valid: Required[bool] """Whether the deployment is valid. - If invalid, the deployment is immediately archived and marked as 'failed'. If - valid, the deployment continues to its desired target status (i.e. 'pending', - 'approved', or 'deployed'). + If invalid, the deployment is immediately archived and marked as 'failed'. """ message: Required[str] diff --git a/src/miru_server_sdk/types/deployment_validate_webhook_event.py b/src/miru_server_sdk/types/deployment_validate_webhook_event.py new file mode 100644 index 0000000..2d96225 --- /dev/null +++ b/src/miru_server_sdk/types/deployment_validate_webhook_event.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DeploymentValidateWebhookEvent", "Data", "DataDeployment"] + + +class DataDeployment(BaseModel): + id: str + """ID of the deployment""" + + created_at: datetime + """Timestamp of when the device release was created""" + + device_id: str + """ID of the device""" + + object: Literal["deployment"] + + release_id: str + """The version of the release""" + + +class Data(BaseModel): + deployment: DataDeployment + + +class DeploymentValidateWebhookEvent(BaseModel): + data: Data + """The data associated with the event""" + + object: Literal["event"] + """The object that occurred""" + + timestamp: datetime + """The timestamp of the event""" + + type: Literal["deployment.validate"] + """The type of event""" diff --git a/src/miru_server_sdk/types/unwrap_webhook_event.py b/src/miru_server_sdk/types/unwrap_webhook_event.py new file mode 100644 index 0000000..fc66adb --- /dev/null +++ b/src/miru_server_sdk/types/unwrap_webhook_event.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["UnwrapWebhookEvent", "Data", "DataDeployment"] + + +class DataDeployment(BaseModel): + id: str + """ID of the deployment""" + + created_at: datetime + """Timestamp of when the device release was created""" + + device_id: str + """ID of the device""" + + object: Literal["deployment"] + + release_id: str + """The version of the release""" + + +class Data(BaseModel): + deployment: DataDeployment + + +class UnwrapWebhookEvent(BaseModel): + data: Data + """The data associated with the event""" + + object: Literal["event"] + """The object that occurred""" + + timestamp: datetime + """The timestamp of the event""" + + type: Literal["deployment.validate"] + """The type of event""" diff --git a/tests/api_resources/test_deployments.py b/tests/api_resources/test_deployments.py index 7bcb5f3..f10f929 100644 --- a/tests/api_resources/test_deployments.py +++ b/tests/api_resources/test_deployments.py @@ -25,7 +25,7 @@ class TestDeployments: @parametrize def test_method_create(self, client: Miru) -> None: deployment = client.deployments.create( - description="Update safety mode to 'manual'", + description="Deployment for the motion control config instance", device_id="dvc_123", new_config_instances=[ { @@ -47,7 +47,7 @@ def test_method_create(self, client: Miru) -> None: @parametrize def test_method_create_with_all_params(self, client: Miru) -> None: deployment = client.deployments.create( - description="Update safety mode to 'manual'", + description="Deployment for the motion control config instance", device_id="dvc_123", new_config_instances=[ { @@ -71,7 +71,7 @@ def test_method_create_with_all_params(self, client: Miru) -> None: @parametrize def test_raw_response_create(self, client: Miru) -> None: response = client.deployments.with_raw_response.create( - description="Update safety mode to 'manual'", + description="Deployment for the motion control config instance", device_id="dvc_123", new_config_instances=[ { @@ -97,7 +97,7 @@ def test_raw_response_create(self, client: Miru) -> None: @parametrize def test_streaming_response_create(self, client: Miru) -> None: with client.deployments.with_streaming_response.create( - description="Update safety mode to 'manual'", + description="Deployment for the motion control config instance", device_id="dvc_123", new_config_instances=[ { @@ -325,7 +325,7 @@ class TestAsyncDeployments: @parametrize async def test_method_create(self, async_client: AsyncMiru) -> None: deployment = await async_client.deployments.create( - description="Update safety mode to 'manual'", + description="Deployment for the motion control config instance", device_id="dvc_123", new_config_instances=[ { @@ -347,7 +347,7 @@ async def test_method_create(self, async_client: AsyncMiru) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncMiru) -> None: deployment = await async_client.deployments.create( - description="Update safety mode to 'manual'", + description="Deployment for the motion control config instance", device_id="dvc_123", new_config_instances=[ { @@ -371,7 +371,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncMiru) -> N @parametrize async def test_raw_response_create(self, async_client: AsyncMiru) -> None: response = await async_client.deployments.with_raw_response.create( - description="Update safety mode to 'manual'", + description="Deployment for the motion control config instance", device_id="dvc_123", new_config_instances=[ { @@ -397,7 +397,7 @@ async def test_raw_response_create(self, async_client: AsyncMiru) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncMiru) -> None: async with async_client.deployments.with_streaming_response.create( - description="Update safety mode to 'manual'", + description="Deployment for the motion control config instance", device_id="dvc_123", new_config_instances=[ { diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py new file mode 100644 index 0000000..543805d --- /dev/null +++ b/tests/api_resources/test_webhooks.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os + +import pytest + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestWebhooks: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + +class TestAsyncWebhooks: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) diff --git a/tests/test_client.py b/tests/test_client.py index fb773fd..a862c39 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -552,6 +552,14 @@ def test_base_url_env(self) -> None: client = Miru(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" + # explicit environment arg requires explicitness + with update_env(MIRU_BASE_URL="http://localhost:5000/from/env"): + with pytest.raises(ValueError, match=r"you must pass base_url=None"): + Miru(api_key=api_key, _strict_response_validation=True, environment="production") + + client = Miru(base_url=None, api_key=api_key, _strict_response_validation=True, environment="production") + assert str(client.base_url).startswith("https://configs.api.miruml.com/v1") + @pytest.mark.parametrize( "client", [ @@ -1355,6 +1363,16 @@ def test_base_url_env(self) -> None: client = AsyncMiru(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" + # explicit environment arg requires explicitness + with update_env(MIRU_BASE_URL="http://localhost:5000/from/env"): + with pytest.raises(ValueError, match=r"you must pass base_url=None"): + AsyncMiru(api_key=api_key, _strict_response_validation=True, environment="production") + + client = AsyncMiru( + base_url=None, api_key=api_key, _strict_response_validation=True, environment="production" + ) + assert str(client.base_url).startswith("https://configs.api.miruml.com/v1") + @pytest.mark.parametrize( "client", [