chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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"""
|
||||
+174
@@ -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
|
||||
+179
@@ -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
|
||||
+176
@@ -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
|
||||
+203
@@ -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
|
||||
+239
@@ -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
|
||||
+259
@@ -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
|
||||
+77
@@ -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,
|
||||
)
|
||||
)
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user