33from __future__ import annotations
44
55import 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
99import httpx
1010
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
3652class 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 ,
0 commit comments