chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from typing import cast, Optional, Dict, TypedDict
|
||||
|
||||
from httpx._types import ProxyTypes
|
||||
from typing_extensions import Unpack
|
||||
|
||||
from e2b.api.metadata import package_version
|
||||
from e2b.sandbox_domains import is_supported_sandbox_domain
|
||||
|
||||
REQUEST_TIMEOUT: float = 60.0 # 60 seconds
|
||||
|
||||
KEEPALIVE_PING_INTERVAL_SEC = 50 # 50 seconds
|
||||
KEEPALIVE_PING_HEADER = "Keepalive-Ping-Interval"
|
||||
|
||||
|
||||
class ApiParams(TypedDict, total=False):
|
||||
"""
|
||||
Parameters for a request.
|
||||
|
||||
In the case of a sandbox, it applies to all **requests made to the returned sandbox**.
|
||||
"""
|
||||
|
||||
request_timeout: Optional[float]
|
||||
"""Timeout for the request in **seconds**, defaults to 60 seconds."""
|
||||
|
||||
headers: Optional[Dict[str, str]]
|
||||
"""Additional headers to send with the request. Deprecated, use api_headers instead."""
|
||||
|
||||
api_headers: Optional[Dict[str, str]]
|
||||
"""Additional headers to send with E2B API requests."""
|
||||
|
||||
api_key: Optional[str]
|
||||
"""E2B API Key to use for authentication, defaults to `E2B_API_KEY` environment variable."""
|
||||
|
||||
validate_api_key: Optional[bool]
|
||||
"""Whether to validate the format of the E2B API key on the client side.
|
||||
Disable this when your deployment issues API keys that don't match the
|
||||
default `e2b_` format. Defaults to `E2B_VALIDATE_API_KEY` environment
|
||||
variable or `True`."""
|
||||
|
||||
domain: Optional[str]
|
||||
"""E2B domain to use for authentication, defaults to `E2B_DOMAIN` environment variable."""
|
||||
|
||||
api_url: Optional[str]
|
||||
"""URL to use for the API, defaults to `https://api.<domain>`. For internal use only."""
|
||||
|
||||
debug: Optional[bool]
|
||||
"""Whether to use debug mode, defaults to `E2B_DEBUG` environment variable."""
|
||||
|
||||
proxy: Optional[ProxyTypes]
|
||||
"""Proxy to use for the request. In case of a sandbox it applies to all **requests made to the returned sandbox**."""
|
||||
|
||||
sandbox_url: Optional[str]
|
||||
"""URL to connect to sandbox, defaults to `E2B_SANDBOX_URL` environment variable."""
|
||||
|
||||
|
||||
class ApiParamsWithLogger(ApiParams, total=False):
|
||||
""":class:`ApiParams` plus the construction-time ``logger``.
|
||||
|
||||
Internal type returned by :meth:`ConnectionConfig.get_api_params` so that the
|
||||
logger a sandbox was created/connected with keeps propagating to the
|
||||
throwaway ``ConnectionConfig`` that instance control-plane methods rebuild.
|
||||
Unlike :class:`ApiParams`, ``logger`` is not a public per-request option.
|
||||
"""
|
||||
|
||||
logger: Optional[logging.Logger]
|
||||
|
||||
|
||||
class ConnectionConfig:
|
||||
"""
|
||||
Configuration for the connection to the API.
|
||||
"""
|
||||
|
||||
envd_port = 49983
|
||||
|
||||
_integration: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def set_integration(cls, integration: Optional[str]) -> None:
|
||||
"""
|
||||
Identify traffic from an integration wrapping the E2B SDK by appending
|
||||
``integration`` (e.g. ``"e2b-code-interpreter/0.1.0"``) to the
|
||||
``User-Agent`` header of every request.
|
||||
|
||||
Call once at startup, before any ``ConnectionConfig`` is constructed —
|
||||
configs read the value at construction time. Pass ``None`` to clear.
|
||||
|
||||
Internal use only — not part of the public, stable API.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
ConnectionConfig._integration = integration
|
||||
|
||||
@staticmethod
|
||||
def _domain():
|
||||
return os.getenv("E2B_DOMAIN") or "e2b.app"
|
||||
|
||||
@staticmethod
|
||||
def _debug():
|
||||
return os.getenv("E2B_DEBUG", "false").lower() == "true"
|
||||
|
||||
@staticmethod
|
||||
def _api_key():
|
||||
return os.getenv("E2B_API_KEY")
|
||||
|
||||
@staticmethod
|
||||
def _validate_api_key():
|
||||
return os.getenv("E2B_VALIDATE_API_KEY", "true").lower() != "false"
|
||||
|
||||
@staticmethod
|
||||
def _api_url():
|
||||
return os.getenv("E2B_API_URL")
|
||||
|
||||
@staticmethod
|
||||
def _sandbox_url():
|
||||
return os.getenv("E2B_SANDBOX_URL")
|
||||
|
||||
@staticmethod
|
||||
def _access_token():
|
||||
return os.getenv("E2B_ACCESS_TOKEN")
|
||||
|
||||
@staticmethod
|
||||
def _build_user_agent() -> str:
|
||||
user_agent_parts = [f"e2b-python-sdk/{package_version}"]
|
||||
|
||||
if ConnectionConfig._integration:
|
||||
user_agent_parts.append(ConnectionConfig._integration)
|
||||
|
||||
return " ".join(user_agent_parts)
|
||||
|
||||
def _apply_user_agent(
|
||||
self,
|
||||
headers: Dict[str, str],
|
||||
user_agent_override: Optional[str],
|
||||
) -> bool:
|
||||
"""
|
||||
Set the ``User-Agent`` on ``headers``: an explicitly provided value
|
||||
always wins; otherwise the SDK-built one, tagged with the current
|
||||
integration.
|
||||
|
||||
Returns whether the SDK-built value was applied, so callers can keep
|
||||
it in sync with the current integration on later rebuilds.
|
||||
"""
|
||||
if user_agent_override is not None:
|
||||
headers["User-Agent"] = user_agent_override
|
||||
return False
|
||||
|
||||
headers["User-Agent"] = self._build_user_agent()
|
||||
return True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
domain: Optional[str] = None,
|
||||
debug: Optional[bool] = None,
|
||||
api_key: Optional[str] = None,
|
||||
validate_api_key: Optional[bool] = None,
|
||||
api_url: Optional[str] = None,
|
||||
sandbox_url: Optional[str] = None,
|
||||
access_token: Optional[str] = None,
|
||||
request_timeout: Optional[float] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
api_headers: Optional[Dict[str, str]] = None,
|
||||
extra_sandbox_headers: Optional[Dict[str, str]] = None,
|
||||
proxy: Optional[ProxyTypes] = None,
|
||||
logger: Optional[logging.Logger] = None,
|
||||
):
|
||||
self.logger = logger
|
||||
self.domain = domain or ConnectionConfig._domain()
|
||||
self.debug = debug if debug is not None else ConnectionConfig._debug()
|
||||
self.api_key = api_key or ConnectionConfig._api_key()
|
||||
self.validate_api_key = (
|
||||
validate_api_key
|
||||
if validate_api_key is not None
|
||||
else ConnectionConfig._validate_api_key()
|
||||
)
|
||||
# Deprecated: pass the token through `api_headers` instead, e.g.
|
||||
# api_headers={"Authorization": f"Bearer {token}"}.
|
||||
self.access_token = access_token or ConnectionConfig._access_token()
|
||||
self.headers = {**(headers or {}), **(api_headers or {})}
|
||||
self._user_agent_is_sdk_built = self._apply_user_agent(
|
||||
self.headers,
|
||||
self.headers.get("User-Agent"),
|
||||
)
|
||||
self.__extra_sandbox_headers = extra_sandbox_headers or {}
|
||||
|
||||
self.proxy = proxy
|
||||
|
||||
self.request_timeout = ConnectionConfig._get_request_timeout(
|
||||
REQUEST_TIMEOUT,
|
||||
request_timeout,
|
||||
)
|
||||
|
||||
self.api_url = (
|
||||
api_url
|
||||
or ConnectionConfig._api_url()
|
||||
or ("http://localhost:3000" if self.debug else f"https://api.{self.domain}")
|
||||
)
|
||||
|
||||
self._sandbox_url: Optional[str] = (
|
||||
sandbox_url or ConnectionConfig._sandbox_url()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_request_timeout(
|
||||
default_timeout: Optional[float],
|
||||
request_timeout: Optional[float],
|
||||
):
|
||||
if request_timeout == 0:
|
||||
return None
|
||||
elif request_timeout is not None:
|
||||
return request_timeout
|
||||
else:
|
||||
return default_timeout
|
||||
|
||||
def get_request_timeout(self, request_timeout: Optional[float] = None):
|
||||
return self._get_request_timeout(self.request_timeout, request_timeout)
|
||||
|
||||
def get_sandbox_url(self, sandbox_id: str, sandbox_domain: str) -> str:
|
||||
if self._sandbox_url:
|
||||
return self._sandbox_url # type: ignore[return-value]
|
||||
|
||||
if self.debug:
|
||||
return f"http://{self.get_host(sandbox_id, sandbox_domain, self.envd_port)}"
|
||||
|
||||
sandbox_domain = sandbox_domain or self.domain
|
||||
if is_supported_sandbox_domain(sandbox_domain):
|
||||
return f"https://sandbox.{sandbox_domain}"
|
||||
|
||||
return f"https://{self.get_host(sandbox_id, sandbox_domain, self.envd_port)}"
|
||||
|
||||
def get_sandbox_direct_url(self, sandbox_id: str, sandbox_domain: str) -> str:
|
||||
if self._sandbox_url:
|
||||
return self._sandbox_url # type: ignore[return-value]
|
||||
|
||||
if self.debug:
|
||||
return f"http://{self.get_host(sandbox_id, sandbox_domain, self.envd_port)}"
|
||||
|
||||
return f"https://{self.get_host(sandbox_id, sandbox_domain, self.envd_port)}"
|
||||
|
||||
def get_host(self, sandbox_id: str, sandbox_domain: str, port: int) -> str:
|
||||
"""
|
||||
Get the host address to connect to the sandbox.
|
||||
You can then use this address to connect to the sandbox port from outside the sandbox via HTTP or WebSocket.
|
||||
|
||||
:param port: Port to connect to
|
||||
:param sandbox_domain: Domain to connect to
|
||||
:param sandbox_id: Sandbox to connect to
|
||||
|
||||
:return: Host address to connect to
|
||||
"""
|
||||
if self.debug:
|
||||
return f"localhost:{port}"
|
||||
|
||||
return f"{port}-{sandbox_id}.{sandbox_domain}"
|
||||
|
||||
def get_api_params(
|
||||
self,
|
||||
**opts: Unpack[ApiParams],
|
||||
) -> dict:
|
||||
"""
|
||||
Get the parameters for the API call.
|
||||
|
||||
This is used to avoid passing the following attributes to the API call:
|
||||
- access_token
|
||||
- api_url
|
||||
|
||||
It also returns a copy, so the original object is not modified.
|
||||
|
||||
:return: Dictionary of parameters for the API call
|
||||
"""
|
||||
headers = opts.get("headers")
|
||||
api_headers = opts.get("api_headers")
|
||||
request_timeout = opts.get("request_timeout")
|
||||
api_key = opts.get("api_key")
|
||||
validate_api_key = opts.get("validate_api_key")
|
||||
api_url = opts.get("api_url")
|
||||
domain = opts.get("domain")
|
||||
debug = opts.get("debug")
|
||||
proxy = opts.get("proxy")
|
||||
sandbox_url = opts.get("sandbox_url")
|
||||
|
||||
req_headers = self.headers.copy()
|
||||
if headers is not None:
|
||||
req_headers.update(headers)
|
||||
if api_headers is not None:
|
||||
req_headers.update(api_headers)
|
||||
if self._user_agent_is_sdk_built:
|
||||
# Same precedence as the merge above: api_headers wins over headers.
|
||||
per_call_user_agent = {**(headers or {}), **(api_headers or {})}.get(
|
||||
"User-Agent"
|
||||
)
|
||||
self._apply_user_agent(req_headers, per_call_user_agent)
|
||||
|
||||
# `logger` is a construction-time option rather than a per-request
|
||||
# ApiParams field, but it must propagate to the throwaway
|
||||
# ConnectionConfig that instance control-plane methods (kill, pause,
|
||||
# set_timeout, get_info, connect, ...) rebuild from these params, so
|
||||
# those requests keep logging with the logger the sandbox was created
|
||||
# or connected with.
|
||||
return dict(
|
||||
ApiParamsWithLogger(
|
||||
api_key=api_key if api_key is not None else self.api_key,
|
||||
validate_api_key=(
|
||||
validate_api_key
|
||||
if validate_api_key is not None
|
||||
else self.validate_api_key
|
||||
),
|
||||
api_url=api_url if api_url is not None else self.api_url,
|
||||
domain=domain if domain is not None else self.domain,
|
||||
debug=debug if debug is not None else self.debug,
|
||||
request_timeout=self.get_request_timeout(request_timeout),
|
||||
headers=req_headers,
|
||||
proxy=proxy if proxy is not None else self.proxy,
|
||||
sandbox_url=(
|
||||
sandbox_url
|
||||
if sandbox_url is not None
|
||||
else cast(Optional[str], self._sandbox_url)
|
||||
),
|
||||
logger=self.logger,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def sandbox_headers(self):
|
||||
"""
|
||||
We need this separate as we use the same header for E2B access token to API and envd access token to sandbox.
|
||||
"""
|
||||
return {
|
||||
"User-Agent": self.headers["User-Agent"],
|
||||
**self.__extra_sandbox_headers,
|
||||
}
|
||||
|
||||
|
||||
Username = str
|
||||
"""
|
||||
User used for the operation in the sandbox.
|
||||
"""
|
||||
|
||||
default_username: Username = "user"
|
||||
"""
|
||||
Default user used for the operation in the sandbox.
|
||||
"""
|
||||
Reference in New Issue
Block a user