chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:58 +08:00
commit b16403ea71
789 changed files with 115226 additions and 0 deletions
@@ -0,0 +1,8 @@
"""A client library for accessing E2B API"""
from .client import AuthenticatedClient, Client
__all__ = (
"AuthenticatedClient",
"Client",
)
@@ -0,0 +1 @@
"""Contains methods for accessing the API"""
@@ -0,0 +1 @@
"""Contains endpoint functions for accessing the API"""
@@ -0,0 +1,174 @@
from http import HTTPStatus
from typing import Any, Optional, Union, cast
import httpx
from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.error import Error
from ...types import UNSET, Response
def _get_kwargs(
volume_id: str,
*,
path: str,
) -> dict[str, Any]:
params: dict[str, Any] = {}
params["path"] = path
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
_kwargs: dict[str, Any] = {
"method": "delete",
"url": f"/volumecontent/{volume_id}/path",
"params": params,
}
return _kwargs
def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, Error]]:
if response.status_code == 204:
response_204 = cast(Any, None)
return response_204
if response.status_code == 404:
response_404 = Error.from_dict(response.json())
return response_404
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None
def _build_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Response[Union[Any, Error]]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)
def sync_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Response[Union[Any, Error]]:
"""Delete a path
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
)
response = client.get_httpx_client().request(
**kwargs,
)
return _build_response(client=client, response=response)
def sync(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Optional[Union[Any, Error]]:
"""Delete a path
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error]
"""
return sync_detailed(
volume_id=volume_id,
client=client,
path=path,
).parsed
async def asyncio_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Response[Union[Any, Error]]:
"""Delete a path
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
)
response = await client.get_async_httpx_client().request(**kwargs)
return _build_response(client=client, response=response)
async def asyncio(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Optional[Union[Any, Error]]:
"""Delete a path
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error]
"""
return (
await asyncio_detailed(
volume_id=volume_id,
client=client,
path=path,
)
).parsed
@@ -0,0 +1,204 @@
from http import HTTPStatus
from typing import Any, Optional, Union, cast
import httpx
from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.error import Error
from ...models.volume_entry_stat import VolumeEntryStat
from ...types import UNSET, Response, Unset
def _get_kwargs(
volume_id: str,
*,
path: str,
depth: Union[Unset, int] = 1,
) -> dict[str, Any]:
params: dict[str, Any] = {}
params["path"] = path
params["depth"] = depth
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
_kwargs: dict[str, Any] = {
"method": "get",
"url": f"/volumecontent/{volume_id}/dir",
"params": params,
}
return _kwargs
def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, Error, list["VolumeEntryStat"]]]:
if response.status_code == 200:
response_200 = []
_response_200 = response.json()
for componentsschemas_volume_directory_listing_item_data in _response_200:
componentsschemas_volume_directory_listing_item = VolumeEntryStat.from_dict(
componentsschemas_volume_directory_listing_item_data
)
response_200.append(componentsschemas_volume_directory_listing_item)
return response_200
if response.status_code == 400:
response_400 = cast(Any, None)
return response_400
if response.status_code == 404:
response_404 = cast(Any, None)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None
def _build_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Response[Union[Any, Error, list["VolumeEntryStat"]]]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)
def sync_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
depth: Union[Unset, int] = 1,
) -> Response[Union[Any, Error, list["VolumeEntryStat"]]]:
"""List directory contents
Args:
volume_id (str):
path (str):
depth (Union[Unset, int]): Default: 1.
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error, list['VolumeEntryStat']]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
depth=depth,
)
response = client.get_httpx_client().request(
**kwargs,
)
return _build_response(client=client, response=response)
def sync(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
depth: Union[Unset, int] = 1,
) -> Optional[Union[Any, Error, list["VolumeEntryStat"]]]:
"""List directory contents
Args:
volume_id (str):
path (str):
depth (Union[Unset, int]): Default: 1.
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error, list['VolumeEntryStat']]
"""
return sync_detailed(
volume_id=volume_id,
client=client,
path=path,
depth=depth,
).parsed
async def asyncio_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
depth: Union[Unset, int] = 1,
) -> Response[Union[Any, Error, list["VolumeEntryStat"]]]:
"""List directory contents
Args:
volume_id (str):
path (str):
depth (Union[Unset, int]): Default: 1.
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error, list['VolumeEntryStat']]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
depth=depth,
)
response = await client.get_async_httpx_client().request(**kwargs)
return _build_response(client=client, response=response)
async def asyncio(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
depth: Union[Unset, int] = 1,
) -> Optional[Union[Any, Error, list["VolumeEntryStat"]]]:
"""List directory contents
Args:
volume_id (str):
path (str):
depth (Union[Unset, int]): Default: 1.
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error, list['VolumeEntryStat']]
"""
return (
await asyncio_detailed(
volume_id=volume_id,
client=client,
path=path,
depth=depth,
)
).parsed
@@ -0,0 +1,179 @@
from http import HTTPStatus
from io import BytesIO
from typing import Any, Optional, Union, cast
import httpx
from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.error import Error
from ...types import UNSET, File, Response
def _get_kwargs(
volume_id: str,
*,
path: str,
) -> dict[str, Any]:
params: dict[str, Any] = {}
params["path"] = path
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
_kwargs: dict[str, Any] = {
"method": "get",
"url": f"/volumecontent/{volume_id}/file",
"params": params,
}
return _kwargs
def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, Error, File]]:
if response.status_code == 200:
response_200 = File(payload=BytesIO(response.content))
return response_200
if response.status_code == 404:
response_404 = cast(Any, None)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None
def _build_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Response[Union[Any, Error, File]]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)
def sync_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Response[Union[Any, Error, File]]:
"""Download file
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error, File]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
)
response = client.get_httpx_client().request(
**kwargs,
)
return _build_response(client=client, response=response)
def sync(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Optional[Union[Any, Error, File]]:
"""Download file
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error, File]
"""
return sync_detailed(
volume_id=volume_id,
client=client,
path=path,
).parsed
async def asyncio_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Response[Union[Any, Error, File]]:
"""Download file
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error, File]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
)
response = await client.get_async_httpx_client().request(**kwargs)
return _build_response(client=client, response=response)
async def asyncio(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Optional[Union[Any, Error, File]]:
"""Download file
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error, File]
"""
return (
await asyncio_detailed(
volume_id=volume_id,
client=client,
path=path,
)
).parsed
@@ -0,0 +1,176 @@
from http import HTTPStatus
from typing import Any, Optional, Union
import httpx
from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.error import Error
from ...models.volume_entry_stat import VolumeEntryStat
from ...types import UNSET, Response
def _get_kwargs(
volume_id: str,
*,
path: str,
) -> dict[str, Any]:
params: dict[str, Any] = {}
params["path"] = path
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
_kwargs: dict[str, Any] = {
"method": "get",
"url": f"/volumecontent/{volume_id}/path",
"params": params,
}
return _kwargs
def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Error, VolumeEntryStat]]:
if response.status_code == 200:
response_200 = VolumeEntryStat.from_dict(response.json())
return response_200
if response.status_code == 404:
response_404 = Error.from_dict(response.json())
return response_404
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None
def _build_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Response[Union[Error, VolumeEntryStat]]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)
def sync_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Response[Union[Error, VolumeEntryStat]]:
"""Get path information
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Error, VolumeEntryStat]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
)
response = client.get_httpx_client().request(
**kwargs,
)
return _build_response(client=client, response=response)
def sync(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Optional[Union[Error, VolumeEntryStat]]:
"""Get path information
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Error, VolumeEntryStat]
"""
return sync_detailed(
volume_id=volume_id,
client=client,
path=path,
).parsed
async def asyncio_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Response[Union[Error, VolumeEntryStat]]:
"""Get path information
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Error, VolumeEntryStat]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
)
response = await client.get_async_httpx_client().request(**kwargs)
return _build_response(client=client, response=response)
async def asyncio(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
) -> Optional[Union[Error, VolumeEntryStat]]:
"""Get path information
Args:
volume_id (str):
path (str):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Error, VolumeEntryStat]
"""
return (
await asyncio_detailed(
volume_id=volume_id,
client=client,
path=path,
)
).parsed
@@ -0,0 +1,203 @@
from http import HTTPStatus
from typing import Any, Optional, Union, cast
import httpx
from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.patch_volumecontent_volume_id_path_body import (
PatchVolumecontentVolumeIDPathBody,
)
from ...models.volume_entry_stat import VolumeEntryStat
from ...types import UNSET, Response
def _get_kwargs(
volume_id: str,
*,
body: PatchVolumecontentVolumeIDPathBody,
path: str,
) -> dict[str, Any]:
headers: dict[str, Any] = {}
params: dict[str, Any] = {}
params["path"] = path
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
_kwargs: dict[str, Any] = {
"method": "patch",
"url": f"/volumecontent/{volume_id}/path",
"params": params,
}
_kwargs["json"] = body.to_dict()
headers["Content-Type"] = "application/json"
_kwargs["headers"] = headers
return _kwargs
def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, VolumeEntryStat]]:
if response.status_code == 200:
response_200 = VolumeEntryStat.from_dict(response.json())
return response_200
if response.status_code == 400:
response_400 = cast(Any, None)
return response_400
if response.status_code == 404:
response_404 = cast(Any, None)
return response_404
if response.status_code == 500:
response_500 = cast(Any, None)
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None
def _build_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Response[Union[Any, VolumeEntryStat]]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)
def sync_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
body: PatchVolumecontentVolumeIDPathBody,
path: str,
) -> Response[Union[Any, VolumeEntryStat]]:
"""Update path metadata
Args:
volume_id (str):
path (str):
body (PatchVolumecontentVolumeIDPathBody):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, VolumeEntryStat]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
body=body,
path=path,
)
response = client.get_httpx_client().request(
**kwargs,
)
return _build_response(client=client, response=response)
def sync(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
body: PatchVolumecontentVolumeIDPathBody,
path: str,
) -> Optional[Union[Any, VolumeEntryStat]]:
"""Update path metadata
Args:
volume_id (str):
path (str):
body (PatchVolumecontentVolumeIDPathBody):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, VolumeEntryStat]
"""
return sync_detailed(
volume_id=volume_id,
client=client,
body=body,
path=path,
).parsed
async def asyncio_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
body: PatchVolumecontentVolumeIDPathBody,
path: str,
) -> Response[Union[Any, VolumeEntryStat]]:
"""Update path metadata
Args:
volume_id (str):
path (str):
body (PatchVolumecontentVolumeIDPathBody):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, VolumeEntryStat]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
body=body,
path=path,
)
response = await client.get_async_httpx_client().request(**kwargs)
return _build_response(client=client, response=response)
async def asyncio(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
body: PatchVolumecontentVolumeIDPathBody,
path: str,
) -> Optional[Union[Any, VolumeEntryStat]]:
"""Update path metadata
Args:
volume_id (str):
path (str):
body (PatchVolumecontentVolumeIDPathBody):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, VolumeEntryStat]
"""
return (
await asyncio_detailed(
volume_id=volume_id,
client=client,
body=body,
path=path,
)
).parsed
@@ -0,0 +1,239 @@
from http import HTTPStatus
from typing import Any, Optional, Union, cast
import httpx
from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.error import Error
from ...models.volume_entry_stat import VolumeEntryStat
from ...types import UNSET, Response, Unset
def _get_kwargs(
volume_id: str,
*,
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> dict[str, Any]:
params: dict[str, Any] = {}
params["path"] = path
params["uid"] = uid
params["gid"] = gid
params["mode"] = mode
params["force"] = force
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
_kwargs: dict[str, Any] = {
"method": "post",
"url": f"/volumecontent/{volume_id}/dir",
"params": params,
}
return _kwargs
def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, Error, VolumeEntryStat]]:
if response.status_code == 201:
response_201 = VolumeEntryStat.from_dict(response.json())
return response_201
if response.status_code == 404:
response_404 = cast(Any, None)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None
def _build_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Response[Union[Any, Error, VolumeEntryStat]]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)
def sync_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> Response[Union[Any, Error, VolumeEntryStat]]:
"""Create a directory
Args:
volume_id (str):
path (str):
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
force (Union[Unset, bool]):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error, VolumeEntryStat]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
uid=uid,
gid=gid,
mode=mode,
force=force,
)
response = client.get_httpx_client().request(
**kwargs,
)
return _build_response(client=client, response=response)
def sync(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> Optional[Union[Any, Error, VolumeEntryStat]]:
"""Create a directory
Args:
volume_id (str):
path (str):
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
force (Union[Unset, bool]):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error, VolumeEntryStat]
"""
return sync_detailed(
volume_id=volume_id,
client=client,
path=path,
uid=uid,
gid=gid,
mode=mode,
force=force,
).parsed
async def asyncio_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> Response[Union[Any, Error, VolumeEntryStat]]:
"""Create a directory
Args:
volume_id (str):
path (str):
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
force (Union[Unset, bool]):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error, VolumeEntryStat]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
path=path,
uid=uid,
gid=gid,
mode=mode,
force=force,
)
response = await client.get_async_httpx_client().request(**kwargs)
return _build_response(client=client, response=response)
async def asyncio(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> Optional[Union[Any, Error, VolumeEntryStat]]:
"""Create a directory
Args:
volume_id (str):
path (str):
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
force (Union[Unset, bool]):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error, VolumeEntryStat]
"""
return (
await asyncio_detailed(
volume_id=volume_id,
client=client,
path=path,
uid=uid,
gid=gid,
mode=mode,
force=force,
)
).parsed
@@ -0,0 +1,259 @@
from http import HTTPStatus
from typing import Any, Optional, Union, cast
import httpx
from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.error import Error
from ...models.volume_entry_stat import VolumeEntryStat
from ...types import UNSET, File, Response, Unset
def _get_kwargs(
volume_id: str,
*,
body: File,
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> dict[str, Any]:
headers: dict[str, Any] = {}
params: dict[str, Any] = {}
params["path"] = path
params["uid"] = uid
params["gid"] = gid
params["mode"] = mode
params["force"] = force
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
_kwargs: dict[str, Any] = {
"method": "put",
"url": f"/volumecontent/{volume_id}/file",
"params": params,
}
_kwargs["content"] = body.payload
headers["Content-Type"] = "application/octet-stream"
_kwargs["headers"] = headers
return _kwargs
def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, Error, VolumeEntryStat]]:
if response.status_code == 201:
response_201 = VolumeEntryStat.from_dict(response.json())
return response_201
if response.status_code == 404:
response_404 = cast(Any, None)
return response_404
if response.status_code == 500:
response_500 = Error.from_dict(response.json())
return response_500
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None
def _build_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Response[Union[Any, Error, VolumeEntryStat]]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)
def sync_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
body: File,
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> Response[Union[Any, Error, VolumeEntryStat]]:
"""Upload file
Args:
volume_id (str):
path (str):
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
force (Union[Unset, bool]):
body (File):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error, VolumeEntryStat]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
body=body,
path=path,
uid=uid,
gid=gid,
mode=mode,
force=force,
)
response = client.get_httpx_client().request(
**kwargs,
)
return _build_response(client=client, response=response)
def sync(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
body: File,
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> Optional[Union[Any, Error, VolumeEntryStat]]:
"""Upload file
Args:
volume_id (str):
path (str):
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
force (Union[Unset, bool]):
body (File):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error, VolumeEntryStat]
"""
return sync_detailed(
volume_id=volume_id,
client=client,
body=body,
path=path,
uid=uid,
gid=gid,
mode=mode,
force=force,
).parsed
async def asyncio_detailed(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
body: File,
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> Response[Union[Any, Error, VolumeEntryStat]]:
"""Upload file
Args:
volume_id (str):
path (str):
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
force (Union[Unset, bool]):
body (File):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[Union[Any, Error, VolumeEntryStat]]
"""
kwargs = _get_kwargs(
volume_id=volume_id,
body=body,
path=path,
uid=uid,
gid=gid,
mode=mode,
force=force,
)
response = await client.get_async_httpx_client().request(**kwargs)
return _build_response(client=client, response=response)
async def asyncio(
volume_id: str,
*,
client: Union[AuthenticatedClient, Client],
body: File,
path: str,
uid: Union[Unset, int] = UNSET,
gid: Union[Unset, int] = UNSET,
mode: Union[Unset, int] = UNSET,
force: Union[Unset, bool] = UNSET,
) -> Optional[Union[Any, Error, VolumeEntryStat]]:
"""Upload file
Args:
volume_id (str):
path (str):
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
force (Union[Unset, bool]):
body (File):
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Union[Any, Error, VolumeEntryStat]
"""
return (
await asyncio_detailed(
volume_id=volume_id,
client=client,
body=body,
path=path,
uid=uid,
gid=gid,
mode=mode,
force=force,
)
).parsed
@@ -0,0 +1,286 @@
import ssl
from typing import Any, Optional, Union
import httpx
from attrs import define, evolve, field
@define
class Client:
"""A class for keeping track of data related to the API
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
``cookies``: A dictionary of cookies to be sent with every request
``headers``: A dictionary of headers to be sent with every request
``timeout``: The maximum amount of a time a request can take. API functions will raise
httpx.TimeoutException if this is exceeded.
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
but can be set to False for testing purposes.
``follow_redirects``: Whether or not to follow redirects. Default value is False.
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
Attributes:
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
argument to the constructor.
"""
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
_base_url: str = field(alias="base_url")
_cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
_headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
_timeout: Optional[httpx.Timeout] = field(
default=None, kw_only=True, alias="timeout"
)
_verify_ssl: Union[str, bool, ssl.SSLContext] = field(
default=True, kw_only=True, alias="verify_ssl"
)
_follow_redirects: bool = field(
default=False, kw_only=True, alias="follow_redirects"
)
_httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
_client: Optional[httpx.Client] = field(default=None, init=False)
_async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
def with_headers(self, headers: dict[str, str]) -> "Client":
"""Get a new client matching this one with additional headers"""
if self._client is not None:
self._client.headers.update(headers)
if self._async_client is not None:
self._async_client.headers.update(headers)
return evolve(self, headers={**self._headers, **headers})
def with_cookies(self, cookies: dict[str, str]) -> "Client":
"""Get a new client matching this one with additional cookies"""
if self._client is not None:
self._client.cookies.update(cookies)
if self._async_client is not None:
self._async_client.cookies.update(cookies)
return evolve(self, cookies={**self._cookies, **cookies})
def with_timeout(self, timeout: httpx.Timeout) -> "Client":
"""Get a new client matching this one with a new timeout (in seconds)"""
if self._client is not None:
self._client.timeout = timeout
if self._async_client is not None:
self._async_client.timeout = timeout
return evolve(self, timeout=timeout)
def set_httpx_client(self, client: httpx.Client) -> "Client":
"""Manually set the underlying httpx.Client
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
"""
self._client = client
return self
def get_httpx_client(self) -> httpx.Client:
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
if self._client is None:
self._client = httpx.Client(
base_url=self._base_url,
cookies=self._cookies,
headers=self._headers,
timeout=self._timeout,
verify=self._verify_ssl,
follow_redirects=self._follow_redirects,
**self._httpx_args,
)
return self._client
def __enter__(self) -> "Client":
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
self.get_httpx_client().__enter__()
return self
def __exit__(self, *args: Any, **kwargs: Any) -> None:
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
self.get_httpx_client().__exit__(*args, **kwargs)
def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
"""Manually the underlying httpx.AsyncClient
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
"""
self._async_client = async_client
return self
def get_async_httpx_client(self) -> httpx.AsyncClient:
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
if self._async_client is None:
self._async_client = httpx.AsyncClient(
base_url=self._base_url,
cookies=self._cookies,
headers=self._headers,
timeout=self._timeout,
verify=self._verify_ssl,
follow_redirects=self._follow_redirects,
**self._httpx_args,
)
return self._async_client
async def __aenter__(self) -> "Client":
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
await self.get_async_httpx_client().__aenter__()
return self
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
@define
class AuthenticatedClient:
"""A Client which has been authenticated for use on secured endpoints
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
``cookies``: A dictionary of cookies to be sent with every request
``headers``: A dictionary of headers to be sent with every request
``timeout``: The maximum amount of a time a request can take. API functions will raise
httpx.TimeoutException if this is exceeded.
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
but can be set to False for testing purposes.
``follow_redirects``: Whether or not to follow redirects. Default value is False.
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
Attributes:
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
argument to the constructor.
token: The token to use for authentication
prefix: The prefix to use for the Authorization header
auth_header_name: The name of the Authorization header
"""
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
_base_url: str = field(alias="base_url")
_cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
_headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
_timeout: Optional[httpx.Timeout] = field(
default=None, kw_only=True, alias="timeout"
)
_verify_ssl: Union[str, bool, ssl.SSLContext] = field(
default=True, kw_only=True, alias="verify_ssl"
)
_follow_redirects: bool = field(
default=False, kw_only=True, alias="follow_redirects"
)
_httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
_client: Optional[httpx.Client] = field(default=None, init=False)
_async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
token: str
prefix: str = "Bearer"
auth_header_name: str = "Authorization"
def with_headers(self, headers: dict[str, str]) -> "AuthenticatedClient":
"""Get a new client matching this one with additional headers"""
if self._client is not None:
self._client.headers.update(headers)
if self._async_client is not None:
self._async_client.headers.update(headers)
return evolve(self, headers={**self._headers, **headers})
def with_cookies(self, cookies: dict[str, str]) -> "AuthenticatedClient":
"""Get a new client matching this one with additional cookies"""
if self._client is not None:
self._client.cookies.update(cookies)
if self._async_client is not None:
self._async_client.cookies.update(cookies)
return evolve(self, cookies={**self._cookies, **cookies})
def with_timeout(self, timeout: httpx.Timeout) -> "AuthenticatedClient":
"""Get a new client matching this one with a new timeout (in seconds)"""
if self._client is not None:
self._client.timeout = timeout
if self._async_client is not None:
self._async_client.timeout = timeout
return evolve(self, timeout=timeout)
def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient":
"""Manually set the underlying httpx.Client
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
"""
self._client = client
return self
def get_httpx_client(self) -> httpx.Client:
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
if self._client is None:
self._headers[self.auth_header_name] = (
f"{self.prefix} {self.token}" if self.prefix else self.token
)
self._client = httpx.Client(
base_url=self._base_url,
cookies=self._cookies,
headers=self._headers,
timeout=self._timeout,
verify=self._verify_ssl,
follow_redirects=self._follow_redirects,
**self._httpx_args,
)
return self._client
def __enter__(self) -> "AuthenticatedClient":
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
self.get_httpx_client().__enter__()
return self
def __exit__(self, *args: Any, **kwargs: Any) -> None:
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
self.get_httpx_client().__exit__(*args, **kwargs)
def set_async_httpx_client(
self, async_client: httpx.AsyncClient
) -> "AuthenticatedClient":
"""Manually the underlying httpx.AsyncClient
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
"""
self._async_client = async_client
return self
def get_async_httpx_client(self) -> httpx.AsyncClient:
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
if self._async_client is None:
self._headers[self.auth_header_name] = (
f"{self.prefix} {self.token}" if self.prefix else self.token
)
self._async_client = httpx.AsyncClient(
base_url=self._base_url,
cookies=self._cookies,
headers=self._headers,
timeout=self._timeout,
verify=self._verify_ssl,
follow_redirects=self._follow_redirects,
**self._httpx_args,
)
return self._async_client
async def __aenter__(self) -> "AuthenticatedClient":
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
await self.get_async_httpx_client().__aenter__()
return self
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
@@ -0,0 +1,16 @@
"""Contains shared errors types that can be raised from API functions"""
class UnexpectedStatus(Exception):
"""Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True"""
def __init__(self, status_code: int, content: bytes):
self.status_code = status_code
self.content = content
super().__init__(
f"Unexpected status code: {status_code}\n\nResponse content:\n{content.decode(errors='ignore')}"
)
__all__ = ["UnexpectedStatus"]
@@ -0,0 +1,13 @@
"""Contains all the data models used in inputs/outputs"""
from .error import Error
from .patch_volumecontent_volume_id_path_body import PatchVolumecontentVolumeIDPathBody
from .volume_entry_stat import VolumeEntryStat
from .volume_entry_stat_type import VolumeEntryStatType
__all__ = (
"Error",
"PatchVolumecontentVolumeIDPathBody",
"VolumeEntryStat",
"VolumeEntryStatType",
)
@@ -0,0 +1,67 @@
from collections.abc import Mapping
from typing import Any, TypeVar
from attrs import define as _attrs_define
from attrs import field as _attrs_field
T = TypeVar("T", bound="Error")
@_attrs_define
class Error:
"""
Attributes:
code (str): Error code
message (str): Error message
"""
code: str
message: str
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
def to_dict(self) -> dict[str, Any]:
code = self.code
message = self.message
field_dict: dict[str, Any] = {}
field_dict.update(self.additional_properties)
field_dict.update(
{
"code": code,
"message": message,
}
)
return field_dict
@classmethod
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
d = dict(src_dict)
code = d.pop("code")
message = d.pop("message")
error = cls(
code=code,
message=message,
)
error.additional_properties = d
return error
@property
def additional_keys(self) -> list[str]:
return list(self.additional_properties.keys())
def __getitem__(self, key: str) -> Any:
return self.additional_properties[key]
def __setitem__(self, key: str, value: Any) -> None:
self.additional_properties[key] = value
def __delitem__(self, key: str) -> None:
del self.additional_properties[key]
def __contains__(self, key: str) -> bool:
return key in self.additional_properties
@@ -0,0 +1,77 @@
from collections.abc import Mapping
from typing import Any, TypeVar, Union
from attrs import define as _attrs_define
from attrs import field as _attrs_field
from ..types import UNSET, Unset
T = TypeVar("T", bound="PatchVolumecontentVolumeIDPathBody")
@_attrs_define
class PatchVolumecontentVolumeIDPathBody:
"""
Attributes:
uid (Union[Unset, int]):
gid (Union[Unset, int]):
mode (Union[Unset, int]):
"""
uid: Union[Unset, int] = UNSET
gid: Union[Unset, int] = UNSET
mode: Union[Unset, int] = UNSET
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
def to_dict(self) -> dict[str, Any]:
uid = self.uid
gid = self.gid
mode = self.mode
field_dict: dict[str, Any] = {}
field_dict.update(self.additional_properties)
field_dict.update({})
if uid is not UNSET:
field_dict["uid"] = uid
if gid is not UNSET:
field_dict["gid"] = gid
if mode is not UNSET:
field_dict["mode"] = mode
return field_dict
@classmethod
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
d = dict(src_dict)
uid = d.pop("uid", UNSET)
gid = d.pop("gid", UNSET)
mode = d.pop("mode", UNSET)
patch_volumecontent_volume_id_path_body = cls(
uid=uid,
gid=gid,
mode=mode,
)
patch_volumecontent_volume_id_path_body.additional_properties = d
return patch_volumecontent_volume_id_path_body
@property
def additional_keys(self) -> list[str]:
return list(self.additional_properties.keys())
def __getitem__(self, key: str) -> Any:
return self.additional_properties[key]
def __setitem__(self, key: str, value: Any) -> None:
self.additional_properties[key] = value
def __delitem__(self, key: str) -> None:
del self.additional_properties[key]
def __contains__(self, key: str) -> bool:
return key in self.additional_properties
@@ -0,0 +1,145 @@
import datetime
from collections.abc import Mapping
from typing import Any, TypeVar, Union
from attrs import define as _attrs_define
from attrs import field as _attrs_field
from dateutil.parser import isoparse
from ..models.volume_entry_stat_type import VolumeEntryStatType
from ..types import UNSET, Unset
T = TypeVar("T", bound="VolumeEntryStat")
@_attrs_define
class VolumeEntryStat:
"""
Attributes:
name (str):
type_ (VolumeEntryStatType):
path (str):
size (int):
mode (int):
uid (int):
gid (int):
atime (datetime.datetime):
mtime (datetime.datetime):
ctime (datetime.datetime):
target (Union[Unset, str]):
"""
name: str
type_: VolumeEntryStatType
path: str
size: int
mode: int
uid: int
gid: int
atime: datetime.datetime
mtime: datetime.datetime
ctime: datetime.datetime
target: Union[Unset, str] = UNSET
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
def to_dict(self) -> dict[str, Any]:
name = self.name
type_ = self.type_.value
path = self.path
size = self.size
mode = self.mode
uid = self.uid
gid = self.gid
atime = self.atime.isoformat()
mtime = self.mtime.isoformat()
ctime = self.ctime.isoformat()
target = self.target
field_dict: dict[str, Any] = {}
field_dict.update(self.additional_properties)
field_dict.update(
{
"name": name,
"type": type_,
"path": path,
"size": size,
"mode": mode,
"uid": uid,
"gid": gid,
"atime": atime,
"mtime": mtime,
"ctime": ctime,
}
)
if target is not UNSET:
field_dict["target"] = target
return field_dict
@classmethod
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
d = dict(src_dict)
name = d.pop("name")
type_ = VolumeEntryStatType(d.pop("type"))
path = d.pop("path")
size = d.pop("size")
mode = d.pop("mode")
uid = d.pop("uid")
gid = d.pop("gid")
atime = isoparse(d.pop("atime"))
mtime = isoparse(d.pop("mtime"))
ctime = isoparse(d.pop("ctime"))
target = d.pop("target", UNSET)
volume_entry_stat = cls(
name=name,
type_=type_,
path=path,
size=size,
mode=mode,
uid=uid,
gid=gid,
atime=atime,
mtime=mtime,
ctime=ctime,
target=target,
)
volume_entry_stat.additional_properties = d
return volume_entry_stat
@property
def additional_keys(self) -> list[str]:
return list(self.additional_properties.keys())
def __getitem__(self, key: str) -> Any:
return self.additional_properties[key]
def __setitem__(self, key: str, value: Any) -> None:
self.additional_properties[key] = value
def __delitem__(self, key: str) -> None:
del self.additional_properties[key]
def __contains__(self, key: str) -> bool:
return key in self.additional_properties
@@ -0,0 +1,11 @@
from enum import Enum
class VolumeEntryStatType(str, Enum):
DIRECTORY = "directory"
FILE = "file"
SYMLINK = "symlink"
UNKNOWN = "unknown"
def __str__(self) -> str:
return str(self.value)
@@ -0,0 +1 @@
# Marker file for PEP 561
@@ -0,0 +1,54 @@
"""Contains some shared types for properties"""
from collections.abc import Mapping, MutableMapping
from http import HTTPStatus
from typing import IO, BinaryIO, Generic, Literal, Optional, TypeVar, Union
from attrs import define
class Unset:
def __bool__(self) -> Literal[False]:
return False
UNSET: Unset = Unset()
# The types that `httpx.Client(files=)` can accept, copied from that library.
FileContent = Union[IO[bytes], bytes, str]
FileTypes = Union[
# (filename, file (or bytes), content_type)
tuple[Optional[str], FileContent, Optional[str]],
# (filename, file (or bytes), content_type, headers)
tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
]
RequestFiles = list[tuple[str, FileTypes]]
@define
class File:
"""Contains information for file uploads"""
payload: BinaryIO
file_name: Optional[str] = None
mime_type: Optional[str] = None
def to_tuple(self) -> FileTypes:
"""Return a tuple representation that httpx will accept for multipart/form-data"""
return self.file_name, self.payload, self.mime_type
T = TypeVar("T")
@define
class Response(Generic[T]):
"""A response from an endpoint"""
status_code: HTTPStatus
content: bytes
headers: MutableMapping[str, str]
parsed: Optional[T]
__all__ = ["UNSET", "File", "FileTypes", "RequestFiles", "Response", "Unset"]
@@ -0,0 +1,88 @@
import asyncio
import os
import weakref
from typing import Dict, Optional
import httpx
from httpx import Limits
from httpx._types import ProxyTypes
from e2b.api import make_async_logging_event_hooks
from e2b.api.metadata import default_headers
from e2b.exceptions import AuthenticationException
from e2b.volume.client.client import AuthenticatedClient as AsyncVolumeApiClient
from e2b.volume.connection_config import VolumeConnectionConfig
limits = Limits(
max_keepalive_connections=int(os.getenv("E2B_MAX_KEEPALIVE_CONNECTIONS", "20")),
max_connections=int(os.getenv("E2B_MAX_CONNECTIONS", "2000")),
keepalive_expiry=int(os.getenv("E2B_KEEPALIVE_EXPIRY", "300")),
)
TransportKey = Optional[ProxyTypes]
def get_api_client(config: VolumeConnectionConfig, **kwargs) -> AsyncVolumeApiClient:
if config.access_token is None:
raise AuthenticationException(
"Volume token is required for volume content operations. "
"Use `AsyncVolume.create`/`AsyncVolume.connect` to obtain it "
"or pass `token` in options.",
)
headers = {
**default_headers,
**(config.headers or {}),
}
request_timeout = config.request_timeout
return AsyncVolumeApiClient(
base_url=config.api_url,
token=config.access_token,
auth_header_name="Authorization",
prefix="Bearer",
headers=headers,
timeout=(
httpx.Timeout(request_timeout) if request_timeout is not None else None
),
httpx_args={
"proxy": config.proxy,
"transport": get_transport(config),
"event_hooks": make_async_logging_event_hooks(config.logger),
},
**kwargs,
)
class AsyncTransportWithLogger(httpx.AsyncHTTPTransport):
# Keyed weakly by the event loop object itself, not id(loop) — CPython
# reuses object ids, so a new loop could otherwise inherit a transport
# bound to a previous, closed loop.
_instances: weakref.WeakKeyDictionary[
asyncio.AbstractEventLoop,
Dict[TransportKey, "AsyncTransportWithLogger"],
] = weakref.WeakKeyDictionary()
@property
def pool(self):
return self._pool
def get_transport(config: VolumeConnectionConfig) -> AsyncTransportWithLogger:
loop = asyncio.get_running_loop()
loop_instances = AsyncTransportWithLogger._instances.get(loop)
if loop_instances is None:
loop_instances = {}
AsyncTransportWithLogger._instances[loop] = loop_instances
key: TransportKey = config.proxy
transport = loop_instances.get(key)
if transport is None:
transport = AsyncTransportWithLogger(
limits=limits,
proxy=config.proxy,
)
loop_instances[key] = transport
return transport
@@ -0,0 +1,80 @@
import os
import threading
from typing import Dict, Optional
import httpx
from httpx import Limits
from httpx._types import ProxyTypes
from e2b.api import make_logging_event_hooks
from e2b.api.metadata import default_headers
from e2b.exceptions import AuthenticationException
from e2b.volume.client.client import AuthenticatedClient as VolumeApiClient
from e2b.volume.connection_config import VolumeConnectionConfig
limits = Limits(
max_keepalive_connections=int(os.getenv("E2B_MAX_KEEPALIVE_CONNECTIONS", "20")),
max_connections=int(os.getenv("E2B_MAX_CONNECTIONS", "2000")),
keepalive_expiry=int(os.getenv("E2B_KEEPALIVE_EXPIRY", "300")),
)
TransportKey = Optional[ProxyTypes]
def get_api_client(config: VolumeConnectionConfig, **kwargs) -> VolumeApiClient:
if config.access_token is None:
raise AuthenticationException(
"Volume token is required for volume content operations. "
"Use `Volume.create`/`Volume.connect` to obtain it "
"or pass `token` in options.",
)
headers = {
**default_headers,
**(config.headers or {}),
}
request_timeout = config.request_timeout
return VolumeApiClient(
base_url=config.api_url,
token=config.access_token,
auth_header_name="Authorization",
prefix="Bearer",
headers=headers,
timeout=(
httpx.Timeout(request_timeout) if request_timeout is not None else None
),
httpx_args={
"proxy": config.proxy,
"transport": get_transport(config),
"event_hooks": make_logging_event_hooks(config.logger),
},
**kwargs,
)
class TransportWithLogger(httpx.HTTPTransport):
_thread_local = threading.local()
@property
def pool(self):
return self._pool
def get_transport(config: VolumeConnectionConfig) -> TransportWithLogger:
instances: Dict[TransportKey, TransportWithLogger] = getattr(
TransportWithLogger._thread_local, "instances", {}
)
key: TransportKey = config.proxy
cached = instances.get(key)
if cached is not None:
return cached
transport = TransportWithLogger(
limits=limits,
proxy=config.proxy,
)
instances[key] = transport
TransportWithLogger._thread_local.instances = instances
return transport
@@ -0,0 +1,145 @@
import logging
import os
from typing import Dict, Optional, TypedDict
from httpx._types import ProxyTypes
from typing_extensions import Unpack
from e2b.api.metadata import package_version
REQUEST_TIMEOUT: float = 60.0 # 60 seconds
# Timeout for volume file transfers, which stream large bodies and so must not
# inherit the short REQUEST_TIMEOUT. (Sandbox filesystem streaming instead
# bounds each chunk by the request timeout and leaves the total to the server.)
FILE_TIMEOUT: float = 3600.0 # 1 hour
class VolumeApiParams(TypedDict, total=False):
"""
Parameters for requests made to the volume content API.
"""
domain: Optional[str]
"""Domain to use for the volume API, defaults to `E2B_DOMAIN` or `e2b.app`."""
debug: Optional[bool]
"""Whether to use debug mode, defaults to `E2B_DEBUG` environment variable."""
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."""
token: Optional[str]
"""Volume auth token used for `Authorization: Bearer <token>`."""
api_url: Optional[str]
"""URL to use for the volume API, defaults to `E2B_VOLUME_API_URL` or `https://api.<domain>`."""
proxy: Optional[ProxyTypes]
"""Proxy to use for the request."""
logger: Optional[logging.Logger]
"""Logger used for request and response logging. Accepts a standard library `logging.Logger`."""
class VolumeConnectionConfig:
"""
Configuration for the volume content API.
Uses bearer token authentication and defaults to the volume content host.
"""
@staticmethod
def _domain():
return os.getenv("E2B_DOMAIN") or "e2b.app"
@staticmethod
def _debug():
return os.getenv("E2B_DEBUG", "false").lower() == "true"
@staticmethod
def _volume_api_url():
return os.getenv("E2B_VOLUME_API_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 __init__(
self,
domain: Optional[str] = None,
debug: Optional[bool] = None,
token: Optional[str] = None,
api_url: Optional[str] = None,
request_timeout: Optional[float] = None,
headers: Optional[Dict[str, str]] = None,
proxy: Optional[ProxyTypes] = None,
logger: Optional[logging.Logger] = None,
):
self.logger = logger
self.domain = domain or self._domain()
self.debug = debug if debug is not None else self._debug()
self.api_url = (
api_url
or self._volume_api_url()
or ("http://localhost:8080" if self.debug else f"https://api.{self.domain}")
)
self.access_token = token
self.token = self.access_token
self.proxy = proxy
self.headers = dict(headers) if headers else {}
self.headers["User-Agent"] = f"e2b-python-sdk/{package_version}"
self.request_timeout = self._get_request_timeout(
REQUEST_TIMEOUT, request_timeout
)
def get_request_timeout(self, request_timeout: Optional[float] = None):
return self._get_request_timeout(self.request_timeout, request_timeout)
def get_api_params(
self,
**opts: Unpack[VolumeApiParams],
) -> dict:
"""
Get request parameters for the volume content API.
"""
domain = opts.get("domain")
debug = opts.get("debug")
headers = opts.get("headers")
request_timeout = opts.get("request_timeout")
token = opts.get("token")
api_url = opts.get("api_url")
proxy = opts.get("proxy")
logger = opts.get("logger")
req_headers = self.headers.copy()
if headers is not None:
req_headers.update(headers)
return dict(
VolumeApiParams(
domain=domain if domain is not None else self.domain,
debug=debug if debug is not None else self.debug,
token=token if token is not None else self.token,
api_url=api_url if api_url is not None else self.api_url,
request_timeout=self.get_request_timeout(request_timeout),
headers=req_headers,
proxy=proxy if proxy is not None else self.proxy,
logger=logger if logger is not None else self.logger,
)
)
+62
View File
@@ -0,0 +1,62 @@
import datetime
from dataclasses import dataclass
from typing import Optional
from e2b.volume.client.models.volume_entry_stat_type import VolumeEntryStatType
# Type alias for file type enum
VolumeFileType = VolumeEntryStatType
@dataclass
class VolumeInfo:
"""Information about a volume."""
volume_id: str
"""Volume ID."""
name: str
"""Volume name."""
@dataclass
class VolumeAndToken(VolumeInfo):
"""Information about a volume and its auth token."""
token: str
"""Volume auth token."""
@dataclass
class VolumeEntryStat:
"""Volume entry stat information."""
name: str
"""Name of the filesystem object."""
type: VolumeFileType
"""Type of the filesystem object."""
path: str
"""Path to the filesystem object."""
size: int
"""Size of the filesystem object."""
mode: int
"""Mode of the filesystem object."""
uid: int
"""User ID of the filesystem object."""
gid: int
"""Group ID of the filesystem object."""
atime: datetime.datetime
"""Access time."""
mtime: datetime.datetime
"""Modification time."""
ctime: datetime.datetime
"""Creation time."""
target: Optional[str] = None
"""Target path for symlinks."""
__all__ = [
"VolumeInfo",
"VolumeAndToken",
"VolumeEntryStat",
"VolumeFileType",
]
+52
View File
@@ -0,0 +1,52 @@
import datetime
from typing import Optional
from e2b.volume.client.models import VolumeEntryStat as VolumeEntryStatApi
from e2b.volume.client.types import UNSET
from e2b.volume.types import VolumeEntryStat
def _ensure_utc(dt: datetime.datetime) -> datetime.datetime:
"""Mark a timezone-naive datetime as UTC (API timestamps are UTC)."""
if dt.tzinfo is None:
return dt.replace(tzinfo=datetime.timezone.utc)
return dt
def convert_volume_entry_stat(api_stat: VolumeEntryStatApi) -> VolumeEntryStat:
"""Convert API VolumeEntryStat to SDK VolumeEntryStat."""
target: Optional[str] = None
if api_stat.target is not UNSET and api_stat.target is not None:
target = str(api_stat.target)
return VolumeEntryStat(
name=api_stat.name,
type=api_stat.type_,
path=api_stat.path,
size=api_stat.size,
mode=api_stat.mode,
uid=api_stat.uid,
gid=api_stat.gid,
atime=_ensure_utc(api_stat.atime),
mtime=_ensure_utc(api_stat.mtime),
ctime=_ensure_utc(api_stat.ctime),
target=target,
)
class DualMethod:
"""Descriptor enabling the same name for a static (class-level) and instance method.
When accessed on the class (e.g. ``Volume.get_info``), the static function
is returned. When accessed on an instance (e.g. ``vol.get_info``), the
instance method is returned as a bound method.
"""
def __init__(self, static_fn, instance_fn):
self._static_fn = static_fn
self._instance_fn = instance_fn
def __get__(self, obj, objtype=None):
if obj is None:
return self._static_fn
return self._instance_fn.__get__(obj, objtype)
@@ -0,0 +1,639 @@
from typing import AsyncIterator, IO, List, Literal, Optional, Union, cast, overload
from http import HTTPStatus
import httpx
from httpx._types import ProxyTypes
from typing_extensions import Unpack
from e2b.api import handle_api_exception
from e2b.api.client.api.volumes import (
post_volumes,
get_volumes,
get_volumes_volume_id,
delete_volumes_volume_id,
)
from e2b.api.client.models import (
NewVolume as NewVolumeModel,
Error,
)
from e2b.api.client.types import Response
from e2b.api.client_async import get_api_client as get_core_api_client
from e2b.connection_config import ApiParams, ConnectionConfig
from e2b.exceptions import NotFoundException, VolumeException
from e2b.volume.client.api.volumes import (
get_volumecontent_volume_id_path as get_path,
get_volumecontent_volume_id_dir as get_dir,
post_volumecontent_volume_id_dir as post_dir,
delete_volumecontent_volume_id_path as delete_path,
patch_volumecontent_volume_id_path as patch_path,
put_volumecontent_volume_id_file as put_file,
)
from e2b.volume.client.models import (
Error as VolumeError,
PatchVolumecontentVolumeIDPathBody as PatchPathBody,
VolumeEntryStat as VolumeEntryStatApi,
)
from e2b.volume.client.types import File as FilePayload, UNSET
from e2b.volume.client_async import get_api_client as get_volume_api_client
from e2b.volume.connection_config import (
VolumeApiParams,
VolumeConnectionConfig,
FILE_TIMEOUT,
)
from e2b.volume.types import (
VolumeAndToken,
VolumeInfo,
VolumeEntryStat,
)
from e2b.io_utils import aiter_io_chunks
from e2b.volume.utils import DualMethod, convert_volume_entry_stat
class AsyncVolume:
"""E2B Volume for persistent storage that can be mounted to sandboxes (async)."""
def __init__(
self,
volume_id: str,
name: str,
token: Optional[str] = None,
domain: Optional[str] = None,
debug: Optional[bool] = None,
proxy: Optional[ProxyTypes] = None,
):
self._volume_id = volume_id
self._name = name
self._token = token
self._domain = domain
self._debug = debug
self._proxy = proxy
@property
def volume_id(self) -> str:
return self._volume_id
@property
def name(self) -> str:
return self._name
@property
def token(self) -> Optional[str]:
return self._token
def _get_volume_config(
self, **opts: Unpack[VolumeApiParams]
) -> VolumeConnectionConfig:
return VolumeConnectionConfig(
domain=opts.get("domain") or self._domain,
debug=opts.get("debug") if opts.get("debug") is not None else self._debug,
token=opts.get("token") or self._token,
api_url=opts.get("api_url"),
request_timeout=opts.get("request_timeout"),
headers=opts.get("headers"),
logger=opts.get("logger"),
proxy=opts.get("proxy") if opts.get("proxy") is not None else self._proxy,
)
@classmethod
async def create(cls, name: str, **opts: Unpack[ApiParams]) -> "AsyncVolume":
"""
Create a new volume.
:param name: Name of the volume
:return: An AsyncVolume instance for the new volume
"""
config = ConnectionConfig(**opts)
api_client = get_core_api_client(config)
res = await post_volumes.asyncio_detailed(
body=NewVolumeModel(name=name),
client=api_client,
)
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, Error):
raise Exception(f"{res.parsed.message}: Request failed")
vol = cls(
volume_id=res.parsed.volume_id,
name=res.parsed.name,
token=res.parsed.token,
domain=config.domain,
debug=config.debug,
proxy=config.proxy,
)
return vol
@classmethod
async def connect(cls, volume_id: str, **opts: Unpack[ApiParams]) -> "AsyncVolume":
"""
Connect to an existing volume by ID.
:param volume_id: Volume ID
:return: An AsyncVolume instance for the existing volume
"""
info = await cls.get_info(volume_id, **opts)
config = ConnectionConfig(**opts)
return cls(
volume_id=volume_id,
name=info.name,
token=info.token,
domain=config.domain,
debug=config.debug,
proxy=config.proxy,
)
@staticmethod
async def _class_get_info(
volume_id: str, **opts: Unpack[ApiParams]
) -> VolumeAndToken:
"""
Get information about a volume.
:param volume_id: Volume ID
:return: Volume info
"""
config = ConnectionConfig(**opts)
api_client = get_core_api_client(config)
res = await get_volumes_volume_id.asyncio_detailed(
volume_id,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Volume {volume_id} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, Error):
raise Exception(f"{res.parsed.message}: Request failed")
return VolumeAndToken(
volume_id=res.parsed.volume_id,
name=res.parsed.name,
token=res.parsed.token,
)
@staticmethod
async def _class_list(**opts: Unpack[ApiParams]) -> List[VolumeInfo]:
"""
List all volumes.
:return: List of volumes
"""
config = ConnectionConfig(**opts)
api_client = get_core_api_client(config)
res = await get_volumes.asyncio_detailed(
client=api_client,
)
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
return []
if isinstance(res.parsed, Error):
raise Exception(f"{res.parsed.message}: Request failed")
return [VolumeInfo(volume_id=v.volume_id, name=v.name) for v in res.parsed]
@staticmethod
async def destroy(volume_id: str, **opts: Unpack[ApiParams]) -> bool:
"""
Destroy a volume.
:param volume_id: Volume ID
"""
config = ConnectionConfig(**opts)
api_client = get_core_api_client(config)
res = await delete_volumes_volume_id.asyncio_detailed(
volume_id,
client=api_client,
)
if res.status_code == 404:
return False
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
return True
async def _instance_list(
self, path: str, depth: Optional[int] = None, **opts: Unpack[VolumeApiParams]
) -> List[VolumeEntryStat]:
"""
List directory contents.
:param path: Path to the directory
:param depth: Number of layers deep to recurse into the directory
:param opts: Connection options
:return: List of items (files and directories) in the directory
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
res = await get_dir.asyncio_detailed(
self._volume_id,
path=path,
depth=depth if depth is not None else UNSET,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
return []
if isinstance(res.parsed, VolumeError):
raise Exception(f"{res.parsed.message}: Request failed")
# VolumeDirectoryListing is a list according to the spec
if isinstance(res.parsed, list):
parsed_entries = cast(List[VolumeEntryStatApi], res.parsed)
return [convert_volume_entry_stat(entry) for entry in parsed_entries]
return []
async def make_dir(
self,
path: str,
uid: Optional[int] = None,
gid: Optional[int] = None,
mode: Optional[int] = None,
force: Optional[bool] = None,
**opts: Unpack[VolumeApiParams],
) -> VolumeEntryStat:
"""
Create a directory.
:param path: Path to the directory to create
:param uid: User ID of the created directory
:param gid: Group ID of the created directory
:param mode: Mode of the created directory
:param force: Create parent directories if they don't exist
:param opts: Connection options
:return: Information about the created directory
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
res = await post_dir.asyncio_detailed(
self._volume_id,
path=path,
uid=uid if uid is not None else UNSET,
gid=gid if gid is not None else UNSET,
mode=mode if mode is not None else UNSET,
force=force if force is not None else UNSET,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, VolumeError):
raise Exception(f"{res.parsed.message}: Request failed")
return convert_volume_entry_stat(res.parsed)
async def exists(self, path: str, **opts: Unpack[VolumeApiParams]) -> bool:
"""
Check whether a file or directory exists.
Uses get_info under the hood. Returns True if the path exists,
False if it does not (404). Other errors are re-raised.
:param path: Path to the file or directory
:param opts: Connection options
:return: True if the path exists, False otherwise
"""
try:
await self.get_info(path, **opts)
return True
except NotFoundException:
return False
async def _instance_get_info(
self, path: str, **opts: Unpack[VolumeApiParams]
) -> VolumeEntryStat:
"""
Get information about a file or directory.
:param path: Path to the file or directory
:param opts: Connection options
:return: Information about the entry
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
res = await get_path.asyncio_detailed(
self._volume_id,
path=path,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, VolumeError):
raise Exception(f"{res.parsed.message}: Request failed")
return convert_volume_entry_stat(cast(VolumeEntryStatApi, res.parsed))
get_info = DualMethod(_class_get_info.__func__, _instance_get_info)
list = DualMethod(_class_list.__func__, _instance_list)
async def update_metadata(
self,
path: str,
uid: Optional[int] = None,
gid: Optional[int] = None,
mode: Optional[int] = None,
**opts: Unpack[VolumeApiParams],
) -> VolumeEntryStat:
"""
Update file or directory metadata.
:param path: Path to the file or directory
:param uid: User ID of the file or directory
:param gid: Group ID of the file or directory
:param mode: Mode of the file or directory
:param opts: Connection options
:return: Updated entry information
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
body = PatchPathBody(
uid=uid if uid is not None else UNSET,
gid=gid if gid is not None else UNSET,
mode=mode if mode is not None else UNSET,
)
res = await patch_path.asyncio_detailed(
self._volume_id,
path=path,
body=body,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
return convert_volume_entry_stat(cast(VolumeEntryStatApi, res.parsed))
@overload
async def read_file(
self,
path: str,
format: Literal["text"] = "text",
**opts: Unpack[VolumeApiParams],
) -> str: ...
@overload
async def read_file(
self,
path: str,
format: Literal["bytes"],
**opts: Unpack[VolumeApiParams],
) -> bytes: ...
@overload
async def read_file(
self,
path: str,
format: Literal["stream"],
stream_idle_timeout: Optional[float] = None,
**opts: Unpack[VolumeApiParams],
) -> AsyncIterator[bytes]: ...
async def read_file(
self,
path: str,
format: Literal["text", "bytes", "stream"] = "text",
stream_idle_timeout: Optional[float] = None,
**opts: Unpack[VolumeApiParams],
) -> Union[str, bytes, AsyncIterator[bytes]]:
"""
Read file content.
You can pass `text`, `bytes`, or `stream` to `format` to change the return type.
:param path: Path to the file
:param format: Format of the file content—`text` by default
:param stream_idle_timeout: Idle timeout in **seconds** for a streamed
read (`format="stream"`)—abort if no chunk arrives within this
window while reading. Resets on every chunk, so it bounds a stalled
stream without limiting total transfer time. Defaults to the request
timeout; pass `0` to disable.
:param opts: Connection options
:return: File content as string, bytes, or async iterator of bytes
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
params = {"path": path}
timeout = VolumeConnectionConfig._get_request_timeout(
FILE_TIMEOUT, opts.get("request_timeout")
)
if format == "stream":
# The request timeout bounds connection setup, not total transfer;
# consuming the body must not be killed by it. httpx's per-chunk
# `read` timeout becomes the idle-read timeout for the body
# (defaults to the request timeout), bounding a stalled stream
# without limiting total transfer time. Pass `0` to disable.
# Mirrors the sandbox files stream path.
idle_timeout = (
timeout if stream_idle_timeout is None else stream_idle_timeout
)
stream_timeout = httpx.Timeout(timeout, read=idle_timeout or None)
async def stream_file() -> AsyncIterator[bytes]:
async with api_client.get_async_httpx_client().stream(
method="GET",
url=f"/volumecontent/{self._volume_id}/file",
params=params,
timeout=stream_timeout,
) as response:
if response.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if response.status_code >= 300:
api_response = Response(
status_code=HTTPStatus(response.status_code),
content=await response.aread(),
headers=response.headers,
parsed=None,
)
raise handle_api_exception(api_response, VolumeException)
async for chunk in response.aiter_bytes():
yield chunk
return stream_file()
response = await api_client.get_async_httpx_client().request(
method="GET",
url=f"/volumecontent/{self._volume_id}/file",
params=params,
timeout=timeout,
)
if response.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if response.status_code >= 300:
api_response = Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=None,
)
raise handle_api_exception(api_response, VolumeException)
if format == "bytes":
return response.content
else:
return response.text
async def write_file(
self,
path: str,
data: Union[str, bytes, IO],
uid: Optional[int] = None,
gid: Optional[int] = None,
mode: Optional[int] = None,
force: Optional[bool] = None,
**opts: Unpack[VolumeApiParams],
) -> VolumeEntryStat:
"""
Write content to a file.
Writing to a file that doesn't exist creates the file.
Writing to a file that already exists overwrites the file.
:param path: Path to the file
:param data: Data to write to the file. Data can be a string, bytes, or IO. File-like objects are streamed in chunks instead of being buffered in memory.
:param uid: User ID of the created file
:param gid: Group ID of the created file
:param mode: Mode of the created file
:param force: Force overwrite of an existing file
:param opts: Connection options
:return: Information about the written file
"""
config = self._get_volume_config(**opts)
upload_timeout = VolumeConnectionConfig._get_request_timeout(
FILE_TIMEOUT, opts.get("request_timeout")
)
api_client = get_volume_api_client(config)
if upload_timeout is not None:
api_client = api_client.with_timeout(httpx.Timeout(upload_timeout))
content: Union[bytes, AsyncIterator[bytes]]
if isinstance(data, str):
content = data.encode("utf-8")
elif isinstance(data, bytes):
content = data
elif hasattr(data, "read"):
# Stream file-like objects in chunks without buffering them in
# memory. Async httpx requires an async iterable request body.
content = aiter_io_chunks(data)
else:
raise ValueError(f"Unsupported data type: {type(data)}")
res = await put_file.asyncio_detailed(
self._volume_id,
body=FilePayload(payload=content), # type: ignore[arg-type] # httpx accepts bytes and streamable content directly
path=path,
uid=uid if uid is not None else UNSET,
gid=gid if gid is not None else UNSET,
mode=mode if mode is not None else UNSET,
force=force if force is not None else UNSET,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, VolumeError):
raise Exception(f"{res.parsed.message}: Request failed")
return convert_volume_entry_stat(cast(VolumeEntryStatApi, res.parsed))
async def remove(
self,
path: str,
**opts: Unpack[VolumeApiParams],
) -> None:
"""
Remove a file or directory.
:param path: Path to the file or directory to remove
:param opts: Connection options
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
res = await delete_path.asyncio_detailed(
self._volume_id,
path=path,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
@@ -0,0 +1,639 @@
import io
from typing import IO, Iterator, List, Literal, Optional, Union, cast, overload
from http import HTTPStatus
import httpx
from httpx._types import ProxyTypes
from typing_extensions import Unpack
from e2b.api import handle_api_exception
from e2b.api.client.api.volumes import (
post_volumes,
get_volumes,
get_volumes_volume_id,
delete_volumes_volume_id,
)
from e2b.api.client.models import (
NewVolume as NewVolumeModel,
Error,
)
from e2b.api.client.types import Response
from e2b.api.client_sync import get_api_client as get_core_api_client
from e2b.connection_config import ApiParams, ConnectionConfig
from e2b.exceptions import NotFoundException, VolumeException
from e2b.volume.client.api.volumes import (
get_volumecontent_volume_id_path as get_path,
get_volumecontent_volume_id_dir as get_dir,
post_volumecontent_volume_id_dir as post_dir,
delete_volumecontent_volume_id_path as delete_path,
patch_volumecontent_volume_id_path as patch_path,
put_volumecontent_volume_id_file as put_file,
)
from e2b.volume.client.models import (
Error as VolumeError,
PatchVolumecontentVolumeIDPathBody as PatchPathBody,
VolumeEntryStat as VolumeEntryStatApi,
)
from e2b.volume.client.types import File as FilePayload, UNSET
from e2b.volume.client_sync import get_api_client as get_volume_api_client
from e2b.volume.connection_config import (
VolumeApiParams,
VolumeConnectionConfig,
FILE_TIMEOUT,
)
from e2b.volume.types import (
VolumeAndToken,
VolumeInfo,
VolumeEntryStat,
)
from e2b.io_utils import iter_io_chunks
from e2b.volume.utils import DualMethod, convert_volume_entry_stat
class Volume:
"""E2B Volume for persistent storage that can be mounted to sandboxes."""
def __init__(
self,
volume_id: str,
name: str,
token: Optional[str] = None,
domain: Optional[str] = None,
debug: Optional[bool] = None,
proxy: Optional[ProxyTypes] = None,
):
self._volume_id = volume_id
self._name = name
self._token = token
self._domain = domain
self._debug = debug
self._proxy = proxy
@property
def volume_id(self) -> str:
return self._volume_id
@property
def name(self) -> str:
return self._name
@property
def token(self) -> Optional[str]:
return self._token
def _get_volume_config(
self, **opts: Unpack[VolumeApiParams]
) -> VolumeConnectionConfig:
return VolumeConnectionConfig(
domain=opts.get("domain") or self._domain,
debug=opts.get("debug") if opts.get("debug") is not None else self._debug,
token=opts.get("token") or self._token,
api_url=opts.get("api_url"),
request_timeout=opts.get("request_timeout"),
headers=opts.get("headers"),
logger=opts.get("logger"),
proxy=opts.get("proxy") if opts.get("proxy") is not None else self._proxy,
)
@classmethod
def create(cls, name: str, **opts: Unpack[ApiParams]) -> "Volume":
"""
Create a new volume.
:param name: Name of the volume
:return: A Volume instance for the new volume
"""
config = ConnectionConfig(**opts)
api_client = get_core_api_client(config)
res = post_volumes.sync_detailed(
body=NewVolumeModel(name=name),
client=api_client,
)
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, Error):
raise Exception(f"{res.parsed.message}: Request failed")
vol = cls(
volume_id=res.parsed.volume_id,
name=res.parsed.name,
token=res.parsed.token,
domain=config.domain,
debug=config.debug,
proxy=config.proxy,
)
return vol
@classmethod
def connect(cls, volume_id: str, **opts: Unpack[ApiParams]) -> "Volume":
"""
Connect to an existing volume by ID.
:param volume_id: Volume ID
:return: A Volume instance for the existing volume
"""
info = cls.get_info(volume_id, **opts)
config = ConnectionConfig(**opts)
return cls(
volume_id=volume_id,
name=info.name,
token=info.token,
domain=config.domain,
debug=config.debug,
proxy=config.proxy,
)
@staticmethod
def _class_get_info(volume_id: str, **opts: Unpack[ApiParams]) -> VolumeAndToken:
"""
Get information about a volume.
:param volume_id: Volume ID
:return: Volume info
"""
config = ConnectionConfig(**opts)
api_client = get_core_api_client(config)
res = get_volumes_volume_id.sync_detailed(
volume_id,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Volume {volume_id} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, Error):
raise Exception(f"{res.parsed.message}: Request failed")
return VolumeAndToken(
volume_id=res.parsed.volume_id,
name=res.parsed.name,
token=res.parsed.token,
)
@staticmethod
def _class_list(**opts: Unpack[ApiParams]) -> List[VolumeInfo]:
"""
List all volumes.
:return: List of volumes
"""
config = ConnectionConfig(**opts)
api_client = get_core_api_client(config)
res = get_volumes.sync_detailed(
client=api_client,
)
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
return []
if isinstance(res.parsed, Error):
raise Exception(f"{res.parsed.message}: Request failed")
return [VolumeInfo(volume_id=v.volume_id, name=v.name) for v in res.parsed]
@staticmethod
def destroy(volume_id: str, **opts: Unpack[ApiParams]) -> bool:
"""
Destroy a volume.
:param volume_id: Volume ID
"""
config = ConnectionConfig(**opts)
api_client = get_core_api_client(config)
res = delete_volumes_volume_id.sync_detailed(
volume_id,
client=api_client,
)
if res.status_code == 404:
return False
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
return True
def _instance_list(
self, path: str, depth: Optional[int] = None, **opts: Unpack[VolumeApiParams]
) -> List[VolumeEntryStat]:
"""
List directory contents.
:param path: Path to the directory
:param depth: Number of layers deep to recurse into the directory
:param opts: Connection options
:return: List of items (files and directories) in the directory
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
res = get_dir.sync_detailed(
self._volume_id,
path=path,
depth=depth if depth is not None else UNSET,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
return []
if isinstance(res.parsed, VolumeError):
raise Exception(f"{res.parsed.message}: Request failed")
# VolumeDirectoryListing is a list according to the spec
if isinstance(res.parsed, list):
parsed_entries = cast(List[VolumeEntryStatApi], res.parsed)
return [convert_volume_entry_stat(entry) for entry in parsed_entries]
return []
def make_dir(
self,
path: str,
uid: Optional[int] = None,
gid: Optional[int] = None,
mode: Optional[int] = None,
force: Optional[bool] = None,
**opts: Unpack[VolumeApiParams],
) -> VolumeEntryStat:
"""
Create a directory.
:param path: Path to the directory to create
:param uid: User ID of the created directory
:param gid: Group ID of the created directory
:param mode: Mode of the created directory
:param force: Create parent directories if they don't exist
:param opts: Connection options
:return: Information about the created directory
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
res = post_dir.sync_detailed(
self._volume_id,
path=path,
uid=uid if uid is not None else UNSET,
gid=gid if gid is not None else UNSET,
mode=mode if mode is not None else UNSET,
force=force if force is not None else UNSET,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, VolumeError):
raise Exception(f"{res.parsed.message}: Request failed")
return convert_volume_entry_stat(res.parsed)
def exists(self, path: str, **opts: Unpack[VolumeApiParams]) -> bool:
"""
Check whether a file or directory exists.
Uses get_info under the hood. Returns True if the path exists,
False if it does not (404). Other errors are re-raised.
:param path: Path to the file or directory
:param opts: Connection options
:return: True if the path exists, False otherwise
"""
try:
self.get_info(path, **opts)
return True
except NotFoundException:
return False
def _instance_get_info(
self, path: str, **opts: Unpack[VolumeApiParams]
) -> VolumeEntryStat:
"""
Get information about a file or directory.
:param path: Path to the file or directory
:param opts: Connection options
:return: Information about the entry
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
res = get_path.sync_detailed(
self._volume_id,
path=path,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, VolumeError):
raise Exception(f"{res.parsed.message}: Request failed")
return convert_volume_entry_stat(cast(VolumeEntryStatApi, res.parsed))
get_info = DualMethod(_class_get_info.__func__, _instance_get_info)
list = DualMethod(_class_list.__func__, _instance_list)
def update_metadata(
self,
path: str,
uid: Optional[int] = None,
gid: Optional[int] = None,
mode: Optional[int] = None,
**opts: Unpack[VolumeApiParams],
) -> VolumeEntryStat:
"""
Update file or directory metadata.
:param path: Path to the file or directory
:param uid: User ID of the file or directory
:param gid: Group ID of the file or directory
:param mode: Mode of the file or directory
:param opts: Connection options
:return: Updated entry information
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
body = PatchPathBody(
uid=uid if uid is not None else UNSET,
gid=gid if gid is not None else UNSET,
mode=mode if mode is not None else UNSET,
)
res = patch_path.sync_detailed(
self._volume_id,
path=path,
body=body,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
return convert_volume_entry_stat(cast(VolumeEntryStatApi, res.parsed))
@overload
def read_file(
self,
path: str,
format: Literal["text"] = "text",
**opts: Unpack[VolumeApiParams],
) -> str: ...
@overload
def read_file(
self,
path: str,
format: Literal["bytes"],
**opts: Unpack[VolumeApiParams],
) -> bytes: ...
@overload
def read_file(
self,
path: str,
format: Literal["stream"],
stream_idle_timeout: Optional[float] = None,
**opts: Unpack[VolumeApiParams],
) -> Iterator[bytes]: ...
def read_file(
self,
path: str,
format: Literal["text", "bytes", "stream"] = "text",
stream_idle_timeout: Optional[float] = None,
**opts: Unpack[VolumeApiParams],
) -> Union[str, bytes, Iterator[bytes]]:
"""
Read file content.
You can pass `text`, `bytes`, or `stream` to `format` to change the return type.
:param path: Path to the file
:param format: Format of the file content—`text` by default
:param stream_idle_timeout: Idle timeout in **seconds** for a streamed
read (`format="stream"`)—abort if no chunk arrives within this
window while reading. Resets on every chunk, so it bounds a stalled
stream without limiting total transfer time. Defaults to the request
timeout; pass `0` to disable.
:param opts: Connection options
:return: File content as string, bytes, or iterator of bytes
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
params = {"path": path}
timeout = VolumeConnectionConfig._get_request_timeout(
FILE_TIMEOUT, opts.get("request_timeout")
)
if format == "stream":
# The request timeout bounds connection setup, not total transfer;
# consuming the body must not be killed by it. httpx's per-chunk
# `read` timeout becomes the idle-read timeout for the body
# (defaults to the request timeout), bounding a stalled stream
# without limiting total transfer time. Pass `0` to disable.
# Mirrors the sandbox files stream path.
idle_timeout = (
timeout if stream_idle_timeout is None else stream_idle_timeout
)
stream_timeout = httpx.Timeout(timeout, read=idle_timeout or None)
def stream_file() -> Iterator[bytes]:
with api_client.get_httpx_client().stream(
method="GET",
url=f"/volumecontent/{self._volume_id}/file",
params=params,
timeout=stream_timeout,
) as response:
if response.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if response.status_code >= 300:
api_response = Response(
status_code=HTTPStatus(response.status_code),
content=response.read(),
headers=response.headers,
parsed=None,
)
raise handle_api_exception(api_response, VolumeException)
yield from response.iter_bytes()
return stream_file()
response = api_client.get_httpx_client().request(
method="GET",
url=f"/volumecontent/{self._volume_id}/file",
params=params,
timeout=timeout,
)
if response.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if response.status_code >= 300:
api_response = Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=None,
)
raise handle_api_exception(api_response, VolumeException)
if format == "bytes":
return response.content
else:
return response.text
def write_file(
self,
path: str,
data: Union[str, bytes, IO],
uid: Optional[int] = None,
gid: Optional[int] = None,
mode: Optional[int] = None,
force: Optional[bool] = None,
**opts: Unpack[VolumeApiParams],
) -> VolumeEntryStat:
"""
Write content to a file.
Writing to a file that doesn't exist creates the file.
Writing to a file that already exists overwrites the file.
:param path: Path to the file
:param data: Data to write to the file. Data can be a string, bytes, or IO. File-like objects are streamed in chunks instead of being buffered in memory.
:param uid: User ID of the created file
:param gid: Group ID of the created file
:param mode: Mode of the created file
:param force: Force overwrite of an existing file
:param opts: Connection options
:return: Information about the written file
"""
config = self._get_volume_config(**opts)
upload_timeout = VolumeConnectionConfig._get_request_timeout(
FILE_TIMEOUT, opts.get("request_timeout")
)
api_client = get_volume_api_client(config)
if upload_timeout is not None:
api_client = api_client.with_timeout(httpx.Timeout(upload_timeout))
content: Union[bytes, IO[bytes], Iterator[bytes]]
if isinstance(data, str):
content = data.encode("utf-8")
elif isinstance(data, bytes):
content = data
elif isinstance(data, io.TextIOBase):
# Text-mode IO yields str chunks—encode them while streaming.
content = iter_io_chunks(data)
elif hasattr(data, "read"):
# httpx streams file-like objects in chunks without buffering.
content = data
else:
raise ValueError(f"Unsupported data type: {type(data)}")
res = put_file.sync_detailed(
self._volume_id,
body=FilePayload(payload=content), # type: ignore[arg-type] # httpx accepts bytes and streamable content directly
path=path,
uid=uid if uid is not None else UNSET,
gid=gid if gid is not None else UNSET,
mode=mode if mode is not None else UNSET,
force=force if force is not None else UNSET,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)
if res.parsed is None:
raise Exception("Body of the request is None")
if isinstance(res.parsed, VolumeError):
raise Exception(f"{res.parsed.message}: Request failed")
return convert_volume_entry_stat(cast(VolumeEntryStatApi, res.parsed))
def remove(
self,
path: str,
**opts: Unpack[VolumeApiParams],
) -> None:
"""
Remove a file or directory.
:param path: Path to the file or directory to remove
:param opts: Connection options
"""
config = self._get_volume_config(**opts)
api_client = get_volume_api_client(config)
res = delete_path.sync_detailed(
self._volume_id,
path=path,
client=api_client,
)
if res.status_code == 404:
raise NotFoundException(f"Path {path} not found")
if res.status_code >= 300:
raise handle_api_exception(res, VolumeException)