Skip to content

Commit 1f24c3f

Browse files
feat(api): manual updates
1 parent 616466d commit 1f24c3f

File tree

7 files changed

+123
-17
lines changed

7 files changed

+123
-17
lines changed

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 14
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/miru-ml%2Fmiru-server-1187ba20c7bd5d91919c6e65f71201c9997541aae5ea6b3068b2e8094b4b8d0c.yml
33
openapi_spec_hash: fd892348c3cd6e7ec5bdce4d8134bf14
4-
config_hash: 1e4fdf81e8b8def15eae344d1f29e513
4+
config_hash: c47912de5d58c4db1a9ed9e3f82a24a0

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ It is generated with [Stainless](https://www.stainless.com/).
1111

1212
## Documentation
1313

14-
The full API of this library can be found in [api.md](api.md).
14+
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).
1515

1616
## Installation
1717

@@ -30,6 +30,8 @@ from miru_server_sdk import Miru
3030

3131
client = Miru(
3232
api_key=os.environ.get("MIRU_SERVER_API_KEY"), # This is the default and can be omitted
33+
# or 'production' | 'local'; defaults to "production".
34+
environment="staging",
3335
)
3436

3537
config_instance = client.config_instances.retrieve(
@@ -54,6 +56,8 @@ from miru_server_sdk import AsyncMiru
5456

5557
client = AsyncMiru(
5658
api_key=os.environ.get("MIRU_SERVER_API_KEY"), # This is the default and can be omitted
59+
# or 'production' | 'local'; defaults to "production".
60+
environment="staging",
5761
)
5862

5963

SECURITY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ before making any information public.
1818
If you encounter security issues that are not directly related to SDKs but pertain to the services
1919
or products provided by Miru, please follow the respective company's security reporting guidelines.
2020

21+
### Miru Terms and Policies
22+
23+
Please contact [email protected] for any questions or concerns regarding the security of our services.
24+
2125
---
2226

2327
Thank you for helping us keep the SDKs and systems they interact with secure.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description = "The official Python library for the miru API"
55
dynamic = ["readme"]
66
license = "MIT"
77
authors = [
8-
{ name = "Miru", email = "" },
8+
{ name = "Miru", email = "[email protected]" },
99
]
1010
dependencies = [
1111
"httpx>=0.23.0, <1",

src/miru_server_sdk/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,18 @@
55
from . import types
66
from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given
77
from ._utils import file_from_path
8-
from ._client import Miru, Client, Stream, Timeout, AsyncMiru, Transport, AsyncClient, AsyncStream, RequestOptions
8+
from ._client import (
9+
ENVIRONMENTS,
10+
Miru,
11+
Client,
12+
Stream,
13+
Timeout,
14+
AsyncMiru,
15+
Transport,
16+
AsyncClient,
17+
AsyncStream,
18+
RequestOptions,
19+
)
920
from ._models import BaseModel
1021
from ._version import __title__, __version__
1122
from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
@@ -63,6 +74,7 @@
6374
"AsyncStream",
6475
"Miru",
6576
"AsyncMiru",
77+
"ENVIRONMENTS",
6678
"file_from_path",
6779
"BaseModel",
6880
"DEFAULT_TIMEOUT",

src/miru_server_sdk/_client.py

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from __future__ import annotations
44

55
import os
6-
from typing import Any, Mapping
7-
from typing_extensions import Self, override
6+
from typing import Any, Dict, Mapping, cast
7+
from typing_extensions import Self, Literal, override
88

99
import httpx
1010

@@ -30,7 +30,23 @@
3030
AsyncAPIClient,
3131
)
3232

33-
__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Miru", "AsyncMiru", "Client", "AsyncClient"]
33+
__all__ = [
34+
"ENVIRONMENTS",
35+
"Timeout",
36+
"Transport",
37+
"ProxiesTypes",
38+
"RequestOptions",
39+
"Miru",
40+
"AsyncMiru",
41+
"Client",
42+
"AsyncClient",
43+
]
44+
45+
ENVIRONMENTS: Dict[str, str] = {
46+
"production": "https://configs.api.miruml.com/v1",
47+
"staging": "https://configs.dev.api.miruml.com/v1",
48+
"local": "http://localhost:8080/v1",
49+
}
3450

3551

3652
class Miru(SyncAPIClient):
@@ -47,13 +63,16 @@ class Miru(SyncAPIClient):
4763
host: str
4864
version: str
4965

66+
_environment: Literal["production", "staging", "local"] | NotGiven
67+
5068
def __init__(
5169
self,
5270
*,
5371
api_key: str | None = None,
5472
host: str | None = None,
5573
version: str | None = None,
56-
base_url: str | httpx.URL | None = None,
74+
environment: Literal["production", "staging", "local"] | NotGiven = not_given,
75+
base_url: str | httpx.URL | None | NotGiven = not_given,
5776
timeout: float | Timeout | None | NotGiven = not_given,
5877
max_retries: int = DEFAULT_MAX_RETRIES,
5978
default_headers: Mapping[str, str] | None = None,
@@ -95,10 +114,31 @@ def __init__(
95114
version = os.environ.get("MIRU_SERVER_VERSION") or "v1"
96115
self.version = version
97116

98-
if base_url is None:
99-
base_url = os.environ.get("MIRU_BASE_URL")
100-
if base_url is None:
101-
base_url = f"https://{host}/{version}"
117+
self._environment = environment
118+
119+
base_url_env = os.environ.get("MIRU_BASE_URL")
120+
if is_given(base_url) and base_url is not None:
121+
# cast required because mypy doesn't understand the type narrowing
122+
base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast]
123+
elif is_given(environment):
124+
if base_url_env and base_url is not None:
125+
raise ValueError(
126+
"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",
127+
)
128+
129+
try:
130+
base_url = ENVIRONMENTS[environment]
131+
except KeyError as exc:
132+
raise ValueError(f"Unknown environment: {environment}") from exc
133+
elif base_url_env is not None:
134+
base_url = base_url_env
135+
else:
136+
self._environment = environment = "production"
137+
138+
try:
139+
base_url = ENVIRONMENTS[environment]
140+
except KeyError as exc:
141+
raise ValueError(f"Unknown environment: {environment}") from exc
102142

103143
super().__init__(
104144
version=__version__,
@@ -145,6 +185,7 @@ def copy(
145185
api_key: str | None = None,
146186
host: str | None = None,
147187
version: str | None = None,
188+
environment: Literal["production", "staging", "local"] | None = None,
148189
base_url: str | httpx.URL | None = None,
149190
timeout: float | Timeout | None | NotGiven = not_given,
150191
http_client: httpx.Client | None = None,
@@ -182,6 +223,7 @@ def copy(
182223
host=host or self.host,
183224
version=version or self.version,
184225
base_url=base_url or self.base_url,
226+
environment=environment or self._environment,
185227
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
186228
http_client=http_client,
187229
max_retries=max_retries if is_given(max_retries) else self.max_retries,
@@ -242,13 +284,16 @@ class AsyncMiru(AsyncAPIClient):
242284
host: str
243285
version: str
244286

287+
_environment: Literal["production", "staging", "local"] | NotGiven
288+
245289
def __init__(
246290
self,
247291
*,
248292
api_key: str | None = None,
249293
host: str | None = None,
250294
version: str | None = None,
251-
base_url: str | httpx.URL | None = None,
295+
environment: Literal["production", "staging", "local"] | NotGiven = not_given,
296+
base_url: str | httpx.URL | None | NotGiven = not_given,
252297
timeout: float | Timeout | None | NotGiven = not_given,
253298
max_retries: int = DEFAULT_MAX_RETRIES,
254299
default_headers: Mapping[str, str] | None = None,
@@ -290,10 +335,31 @@ def __init__(
290335
version = os.environ.get("MIRU_SERVER_VERSION") or "v1"
291336
self.version = version
292337

293-
if base_url is None:
294-
base_url = os.environ.get("MIRU_BASE_URL")
295-
if base_url is None:
296-
base_url = f"https://{host}/{version}"
338+
self._environment = environment
339+
340+
base_url_env = os.environ.get("MIRU_BASE_URL")
341+
if is_given(base_url) and base_url is not None:
342+
# cast required because mypy doesn't understand the type narrowing
343+
base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast]
344+
elif is_given(environment):
345+
if base_url_env and base_url is not None:
346+
raise ValueError(
347+
"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",
348+
)
349+
350+
try:
351+
base_url = ENVIRONMENTS[environment]
352+
except KeyError as exc:
353+
raise ValueError(f"Unknown environment: {environment}") from exc
354+
elif base_url_env is not None:
355+
base_url = base_url_env
356+
else:
357+
self._environment = environment = "production"
358+
359+
try:
360+
base_url = ENVIRONMENTS[environment]
361+
except KeyError as exc:
362+
raise ValueError(f"Unknown environment: {environment}") from exc
297363

298364
super().__init__(
299365
version=__version__,
@@ -340,6 +406,7 @@ def copy(
340406
api_key: str | None = None,
341407
host: str | None = None,
342408
version: str | None = None,
409+
environment: Literal["production", "staging", "local"] | None = None,
343410
base_url: str | httpx.URL | None = None,
344411
timeout: float | Timeout | None | NotGiven = not_given,
345412
http_client: httpx.AsyncClient | None = None,
@@ -377,6 +444,7 @@ def copy(
377444
host=host or self.host,
378445
version=version or self.version,
379446
base_url=base_url or self.base_url,
447+
environment=environment or self._environment,
380448
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
381449
http_client=http_client,
382450
max_retries=max_retries if is_given(max_retries) else self.max_retries,

tests/test_client.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,14 @@ def test_base_url_env(self) -> None:
552552
client = Miru(api_key=api_key, _strict_response_validation=True)
553553
assert client.base_url == "http://localhost:5000/from/env/"
554554

555+
# explicit environment arg requires explicitness
556+
with update_env(MIRU_BASE_URL="http://localhost:5000/from/env"):
557+
with pytest.raises(ValueError, match=r"you must pass base_url=None"):
558+
Miru(api_key=api_key, _strict_response_validation=True, environment="production")
559+
560+
client = Miru(base_url=None, api_key=api_key, _strict_response_validation=True, environment="production")
561+
assert str(client.base_url).startswith("https://configs.api.miruml.com/v1")
562+
555563
@pytest.mark.parametrize(
556564
"client",
557565
[
@@ -1355,6 +1363,16 @@ def test_base_url_env(self) -> None:
13551363
client = AsyncMiru(api_key=api_key, _strict_response_validation=True)
13561364
assert client.base_url == "http://localhost:5000/from/env/"
13571365

1366+
# explicit environment arg requires explicitness
1367+
with update_env(MIRU_BASE_URL="http://localhost:5000/from/env"):
1368+
with pytest.raises(ValueError, match=r"you must pass base_url=None"):
1369+
AsyncMiru(api_key=api_key, _strict_response_validation=True, environment="production")
1370+
1371+
client = AsyncMiru(
1372+
base_url=None, api_key=api_key, _strict_response_validation=True, environment="production"
1373+
)
1374+
assert str(client.base_url).startswith("https://configs.api.miruml.com/v1")
1375+
13581376
@pytest.mark.parametrize(
13591377
"client",
13601378
[

0 commit comments

Comments
 (0)