acontext 0.0.1.dev2__tar.gz → 0.0.1.dev3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/PKG-INFO +3 -2
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/README.md +1 -1
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/pyproject.toml +3 -2
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/_constants.py +0 -1
- acontext-0.0.1.dev3/src/acontext/_utils.py +42 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/client.py +56 -24
- acontext-0.0.1.dev3/src/acontext/resources/blocks.py +162 -0
- acontext-0.0.1.dev3/src/acontext/resources/disks.py +194 -0
- acontext-0.0.1.dev3/src/acontext/resources/sessions.py +255 -0
- acontext-0.0.1.dev3/src/acontext/resources/spaces.py +89 -0
- acontext-0.0.1.dev3/src/acontext/types/__init__.py +54 -0
- acontext-0.0.1.dev3/src/acontext/types/block.py +26 -0
- acontext-0.0.1.dev3/src/acontext/types/disk.py +65 -0
- acontext-0.0.1.dev3/src/acontext/types/session.py +123 -0
- acontext-0.0.1.dev3/src/acontext/types/space.py +24 -0
- acontext-0.0.1.dev2/src/acontext/resources/blocks.py +0 -94
- acontext-0.0.1.dev2/src/acontext/resources/disks.py +0 -109
- acontext-0.0.1.dev2/src/acontext/resources/sessions.py +0 -148
- acontext-0.0.1.dev2/src/acontext/resources/spaces.py +0 -58
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/__init__.py +0 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/client_types.py +0 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/errors.py +0 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/messages.py +0 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/py.typed +0 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/resources/__init__.py +0 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev3}/src/acontext/uploads.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: acontext
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev3
|
|
4
4
|
Summary: Python SDK for the Acontext API
|
|
5
5
|
Keywords: acontext,sdk,client,api
|
|
6
6
|
Requires-Dist: httpx>=0.28.1
|
|
7
7
|
Requires-Dist: openai>=2.6.1
|
|
8
8
|
Requires-Dist: anthropic>=0.72.0
|
|
9
|
+
Requires-Dist: pydantic>=2.12.3
|
|
9
10
|
Requires-Python: >=3.10
|
|
10
11
|
Project-URL: Homepage, https://github.com/memodb-io/Acontext
|
|
11
12
|
Project-URL: Issues, https://github.com/memodb-io/Acontext/issues
|
|
@@ -63,7 +64,7 @@ try:
|
|
|
63
64
|
content=b"# Retro Notes\nWe shipped file uploads successfully!\n",
|
|
64
65
|
content_type="text/markdown",
|
|
65
66
|
),
|
|
66
|
-
file_path="notes/
|
|
67
|
+
file_path="/notes/",
|
|
67
68
|
meta={"source": "readme-demo"},
|
|
68
69
|
)
|
|
69
70
|
finally:
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acontext"
|
|
3
|
-
version = "0.0.1.
|
|
3
|
+
version = "0.0.1.dev3"
|
|
4
4
|
description = "Python SDK for the Acontext API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"httpx>=0.28.1",
|
|
9
9
|
"openai>=2.6.1",
|
|
10
|
-
"anthropic>=0.72.0"
|
|
10
|
+
"anthropic>=0.72.0",
|
|
11
|
+
"pydantic>=2.12.3",
|
|
11
12
|
]
|
|
12
13
|
keywords = ["acontext", "sdk", "client", "api"]
|
|
13
14
|
|
|
@@ -5,7 +5,6 @@ Internal constants shared across the Python SDK.
|
|
|
5
5
|
from importlib import metadata as _metadata
|
|
6
6
|
|
|
7
7
|
DEFAULT_BASE_URL = "https://api.acontext.io/api/v1"
|
|
8
|
-
SUPPORTED_ROLES = {"user", "assistant", "system", "tool", "function"}
|
|
9
8
|
|
|
10
9
|
try:
|
|
11
10
|
_VERSION = _metadata.version("acontext-py")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Utility functions for the acontext Python client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def bool_to_str(value: bool) -> str:
|
|
7
|
+
"""Convert a boolean value to string representation used by the API.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
value: The boolean value to convert.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
"true" if value is True, "false" otherwise.
|
|
14
|
+
"""
|
|
15
|
+
return "true" if value else "false"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_params(**kwargs: Any) -> dict[str, Any]:
|
|
19
|
+
"""Build query parameters dictionary, filtering None values and converting booleans.
|
|
20
|
+
|
|
21
|
+
This function filters out None values and converts boolean values to their
|
|
22
|
+
string representations ("true" or "false") as expected by the API.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
**kwargs: Keyword arguments to build parameters from.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dictionary with non-None parameters, with booleans converted to strings.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> build_params(limit=10, cursor=None, time_desc=True)
|
|
32
|
+
{'limit': 10, 'time_desc': 'true'}
|
|
33
|
+
"""
|
|
34
|
+
params: dict[str, Any] = {}
|
|
35
|
+
for key, value in kwargs.items():
|
|
36
|
+
if value is not None:
|
|
37
|
+
if isinstance(value, bool):
|
|
38
|
+
params[key] = bool_to_str(value)
|
|
39
|
+
else:
|
|
40
|
+
params[key] = value
|
|
41
|
+
return params
|
|
42
|
+
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
High-level synchronous client for the Acontext API.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
from collections.abc import Mapping, MutableMapping
|
|
6
7
|
from typing import Any, BinaryIO
|
|
7
8
|
|
|
@@ -16,41 +17,68 @@ from .resources.blocks import BlocksAPI as BlocksAPI
|
|
|
16
17
|
from .resources.sessions import SessionsAPI as SessionsAPI
|
|
17
18
|
from .resources.spaces import SpacesAPI as SpacesAPI
|
|
18
19
|
|
|
19
|
-
class AcontextClient:
|
|
20
|
-
"""
|
|
21
|
-
Synchronous HTTP client for the Acontext REST API.
|
|
22
|
-
|
|
23
|
-
Example::
|
|
24
|
-
|
|
25
|
-
from acontext import AcontextClient, MessagePart
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
spaces = client.spaces.list()
|
|
29
|
-
session = client.sessions.create(space_id=spaces[0]["id"])
|
|
30
|
-
client.sessions.send_message(
|
|
31
|
-
session["id"],
|
|
32
|
-
role="user",
|
|
33
|
-
parts=[MessagePart.text_part("Hello Acontext!")],
|
|
34
|
-
)
|
|
35
|
-
"""
|
|
21
|
+
class AcontextClient:
|
|
36
22
|
|
|
37
23
|
def __init__(
|
|
38
24
|
self,
|
|
39
25
|
*,
|
|
40
|
-
api_key: str,
|
|
41
|
-
base_url: str =
|
|
26
|
+
api_key: str | None = None,
|
|
27
|
+
base_url: str | None = None,
|
|
42
28
|
timeout: float | httpx.Timeout | None = 10.0,
|
|
43
29
|
user_agent: str | None = None,
|
|
44
30
|
client: httpx.Client | None = None,
|
|
45
31
|
) -> None:
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
"""
|
|
33
|
+
Initialize the Acontext client.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
api_key: API key for authentication. Can also be set via ACONTEXT_API_KEY env var.
|
|
37
|
+
base_url: Base URL for the API. Defaults to DEFAULT_BASE_URL. Can also be set via ACONTEXT_BASE_URL env var.
|
|
38
|
+
timeout: Request timeout in seconds. Defaults to 10.0. Can also be set via ACONTEXT_TIMEOUT env var.
|
|
39
|
+
Can also be an httpx.Timeout object.
|
|
40
|
+
user_agent: Custom user agent string. Can also be set via ACONTEXT_USER_AGENT env var.
|
|
41
|
+
client: Optional httpx.Client instance to reuse. If provided, headers and base_url
|
|
42
|
+
will be merged with the client configuration.
|
|
43
|
+
"""
|
|
44
|
+
# Priority: explicit parameters > environment variables > defaults
|
|
45
|
+
# Load api_key from parameter or environment variable
|
|
46
|
+
api_key = api_key or os.getenv("ACONTEXT_API_KEY")
|
|
47
|
+
if not api_key or not api_key.strip():
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"api_key is required. Provide it either as a parameter (api_key='...') "
|
|
50
|
+
"or set the ACONTEXT_API_KEY environment variable."
|
|
51
|
+
)
|
|
48
52
|
|
|
53
|
+
# Load other parameters from environment variables if not provided
|
|
54
|
+
if base_url is None:
|
|
55
|
+
base_url = os.getenv("ACONTEXT_BASE_URL", DEFAULT_BASE_URL)
|
|
49
56
|
base_url = base_url.rstrip("/")
|
|
57
|
+
|
|
58
|
+
if user_agent is None:
|
|
59
|
+
user_agent = os.getenv("ACONTEXT_USER_AGENT", DEFAULT_USER_AGENT)
|
|
60
|
+
|
|
61
|
+
# Handle timeout: support both float and httpx.Timeout
|
|
62
|
+
if timeout is None:
|
|
63
|
+
timeout_str = os.getenv("ACONTEXT_TIMEOUT")
|
|
64
|
+
if timeout_str:
|
|
65
|
+
try:
|
|
66
|
+
timeout = float(timeout_str)
|
|
67
|
+
except ValueError:
|
|
68
|
+
timeout = 10.0
|
|
69
|
+
else:
|
|
70
|
+
timeout = 10.0
|
|
71
|
+
|
|
72
|
+
# Determine actual timeout value
|
|
73
|
+
if isinstance(timeout, httpx.Timeout):
|
|
74
|
+
actual_timeout = timeout
|
|
75
|
+
else:
|
|
76
|
+
actual_timeout = float(timeout)
|
|
77
|
+
|
|
50
78
|
headers = {
|
|
51
79
|
"Authorization": f"Bearer {api_key}",
|
|
52
80
|
"Accept": "application/json",
|
|
53
|
-
"User-Agent": user_agent
|
|
81
|
+
"User-Agent": user_agent,
|
|
54
82
|
}
|
|
55
83
|
|
|
56
84
|
if client is not None:
|
|
@@ -63,11 +91,15 @@ class AcontextClient:
|
|
|
63
91
|
client.headers[name] = value
|
|
64
92
|
self._base_url = str(client.base_url) or base_url
|
|
65
93
|
else:
|
|
66
|
-
self._client = httpx.Client(
|
|
94
|
+
self._client = httpx.Client(
|
|
95
|
+
base_url=base_url,
|
|
96
|
+
headers=headers,
|
|
97
|
+
timeout=actual_timeout,
|
|
98
|
+
)
|
|
67
99
|
self._owns_client = True
|
|
68
100
|
self._base_url = base_url
|
|
69
101
|
|
|
70
|
-
self._timeout =
|
|
102
|
+
self._timeout = actual_timeout
|
|
71
103
|
|
|
72
104
|
self.spaces = SpacesAPI(self)
|
|
73
105
|
self.sessions = SessionsAPI(self)
|
|
@@ -123,7 +155,7 @@ class AcontextClient:
|
|
|
123
155
|
content_type = response.headers.get("content-type", "")
|
|
124
156
|
|
|
125
157
|
parsed: Mapping[str, Any] | MutableMapping[str, Any] | None
|
|
126
|
-
if "application/json" in content_type
|
|
158
|
+
if "application/json" in content_type:
|
|
127
159
|
try:
|
|
128
160
|
parsed = response.json()
|
|
129
161
|
except ValueError:
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Block endpoints.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, MutableMapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..client_types import RequesterProtocol
|
|
9
|
+
from ..types.block import Block
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BlocksAPI:
|
|
13
|
+
def __init__(self, requester: RequesterProtocol) -> None:
|
|
14
|
+
self._requester = requester
|
|
15
|
+
|
|
16
|
+
def list(
|
|
17
|
+
self,
|
|
18
|
+
space_id: str,
|
|
19
|
+
*,
|
|
20
|
+
parent_id: str | None = None,
|
|
21
|
+
block_type: str | None = None,
|
|
22
|
+
) -> list[Block]:
|
|
23
|
+
"""List blocks in a space.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
space_id: The UUID of the space.
|
|
27
|
+
parent_id: Filter blocks by parent ID. Defaults to None.
|
|
28
|
+
block_type: Filter blocks by type (e.g., "page", "folder", "text", "sop"). Defaults to None.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of Block objects.
|
|
32
|
+
"""
|
|
33
|
+
params: dict[str, Any] = {}
|
|
34
|
+
if parent_id is not None:
|
|
35
|
+
params["parent_id"] = parent_id
|
|
36
|
+
if block_type is not None:
|
|
37
|
+
params["type"] = block_type
|
|
38
|
+
data = self._requester.request("GET", f"/space/{space_id}/block", params=params or None)
|
|
39
|
+
return [Block.model_validate(item) for item in data]
|
|
40
|
+
|
|
41
|
+
def create(
|
|
42
|
+
self,
|
|
43
|
+
space_id: str,
|
|
44
|
+
*,
|
|
45
|
+
block_type: str,
|
|
46
|
+
parent_id: str | None = None,
|
|
47
|
+
title: str | None = None,
|
|
48
|
+
props: Mapping[str, Any] | MutableMapping[str, Any] | None = None,
|
|
49
|
+
) -> Block:
|
|
50
|
+
"""Create a new block in a space.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
space_id: The UUID of the space.
|
|
54
|
+
block_type: The type of block (e.g., "page", "folder", "text", "sop").
|
|
55
|
+
parent_id: Optional parent block ID. Defaults to None.
|
|
56
|
+
title: Optional block title. Defaults to None.
|
|
57
|
+
props: Optional block properties dictionary. Defaults to None.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The created Block object.
|
|
61
|
+
"""
|
|
62
|
+
payload: dict[str, Any] = {"type": block_type}
|
|
63
|
+
if parent_id is not None:
|
|
64
|
+
payload["parent_id"] = parent_id
|
|
65
|
+
if title is not None:
|
|
66
|
+
payload["title"] = title
|
|
67
|
+
if props is not None:
|
|
68
|
+
payload["props"] = props
|
|
69
|
+
data = self._requester.request("POST", f"/space/{space_id}/block", json_data=payload)
|
|
70
|
+
return Block.model_validate(data)
|
|
71
|
+
|
|
72
|
+
def delete(self, space_id: str, block_id: str) -> None:
|
|
73
|
+
"""Delete a block by its ID.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
space_id: The UUID of the space.
|
|
77
|
+
block_id: The UUID of the block to delete.
|
|
78
|
+
"""
|
|
79
|
+
self._requester.request("DELETE", f"/space/{space_id}/block/{block_id}")
|
|
80
|
+
|
|
81
|
+
def get_properties(self, space_id: str, block_id: str) -> Block:
|
|
82
|
+
"""Get block properties.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
space_id: The UUID of the space.
|
|
86
|
+
block_id: The UUID of the block.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Block object containing the properties.
|
|
90
|
+
"""
|
|
91
|
+
data = self._requester.request("GET", f"/space/{space_id}/block/{block_id}/properties")
|
|
92
|
+
return Block.model_validate(data)
|
|
93
|
+
|
|
94
|
+
def update_properties(
|
|
95
|
+
self,
|
|
96
|
+
space_id: str,
|
|
97
|
+
block_id: str,
|
|
98
|
+
*,
|
|
99
|
+
title: str | None = None,
|
|
100
|
+
props: Mapping[str, Any] | MutableMapping[str, Any] | None = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Update block properties.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
space_id: The UUID of the space.
|
|
106
|
+
block_id: The UUID of the block.
|
|
107
|
+
title: Optional block title. Defaults to None.
|
|
108
|
+
props: Optional block properties dictionary. Defaults to None.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValueError: If both title and props are None.
|
|
112
|
+
"""
|
|
113
|
+
payload: dict[str, Any] = {}
|
|
114
|
+
if title is not None:
|
|
115
|
+
payload["title"] = title
|
|
116
|
+
if props is not None:
|
|
117
|
+
payload["props"] = props
|
|
118
|
+
if not payload:
|
|
119
|
+
raise ValueError("title or props must be provided")
|
|
120
|
+
self._requester.request("PUT", f"/space/{space_id}/block/{block_id}/properties", json_data=payload)
|
|
121
|
+
|
|
122
|
+
def move(
|
|
123
|
+
self,
|
|
124
|
+
space_id: str,
|
|
125
|
+
block_id: str,
|
|
126
|
+
*,
|
|
127
|
+
parent_id: str | None = None,
|
|
128
|
+
sort: int | None = None,
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Move a block by updating its parent or sort order.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
space_id: The UUID of the space.
|
|
134
|
+
block_id: The UUID of the block to move.
|
|
135
|
+
parent_id: Optional new parent block ID. Defaults to None.
|
|
136
|
+
sort: Optional new sort order. Defaults to None.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
ValueError: If both parent_id and sort are None.
|
|
140
|
+
"""
|
|
141
|
+
payload: dict[str, Any] = {}
|
|
142
|
+
if parent_id is not None:
|
|
143
|
+
payload["parent_id"] = parent_id
|
|
144
|
+
if sort is not None:
|
|
145
|
+
payload["sort"] = sort
|
|
146
|
+
if not payload:
|
|
147
|
+
raise ValueError("parent_id or sort must be provided")
|
|
148
|
+
self._requester.request("PUT", f"/space/{space_id}/block/{block_id}/move", json_data=payload)
|
|
149
|
+
|
|
150
|
+
def update_sort(self, space_id: str, block_id: str, *, sort: int) -> None:
|
|
151
|
+
"""Update block sort order.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
space_id: The UUID of the space.
|
|
155
|
+
block_id: The UUID of the block.
|
|
156
|
+
sort: The new sort order.
|
|
157
|
+
"""
|
|
158
|
+
self._requester.request(
|
|
159
|
+
"PUT",
|
|
160
|
+
f"/space/{space_id}/block/{block_id}/sort",
|
|
161
|
+
json_data={"sort": sort},
|
|
162
|
+
)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Disk and artifact endpoints.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Mapping, MutableMapping
|
|
7
|
+
from typing import Any, BinaryIO, cast
|
|
8
|
+
|
|
9
|
+
from .._utils import build_params
|
|
10
|
+
from ..client_types import RequesterProtocol
|
|
11
|
+
from ..types.disk import (
|
|
12
|
+
Artifact,
|
|
13
|
+
Disk,
|
|
14
|
+
GetArtifactResp,
|
|
15
|
+
ListArtifactsResp,
|
|
16
|
+
ListDisksOutput,
|
|
17
|
+
UpdateArtifactResp,
|
|
18
|
+
)
|
|
19
|
+
from ..uploads import FileUpload, normalize_file_upload
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DisksAPI:
|
|
23
|
+
def __init__(self, requester: RequesterProtocol) -> None:
|
|
24
|
+
self._requester = requester
|
|
25
|
+
self.artifacts = DiskArtifactsAPI(requester)
|
|
26
|
+
|
|
27
|
+
def list(
|
|
28
|
+
self,
|
|
29
|
+
*,
|
|
30
|
+
limit: int | None = None,
|
|
31
|
+
cursor: str | None = None,
|
|
32
|
+
time_desc: bool | None = None,
|
|
33
|
+
) -> ListDisksOutput:
|
|
34
|
+
"""List all disks in the project.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
limit: Maximum number of disks to return. Defaults to None.
|
|
38
|
+
cursor: Cursor for pagination. Defaults to None.
|
|
39
|
+
time_desc: Order by created_at descending if True, ascending if False. Defaults to None.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
ListDisksOutput containing the list of disks and pagination information.
|
|
43
|
+
"""
|
|
44
|
+
params = build_params(limit=limit, cursor=cursor, time_desc=time_desc)
|
|
45
|
+
data = self._requester.request("GET", "/disk", params=params or None)
|
|
46
|
+
return ListDisksOutput.model_validate(data)
|
|
47
|
+
|
|
48
|
+
def create(self) -> Disk:
|
|
49
|
+
"""Create a new disk.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The created Disk object.
|
|
53
|
+
"""
|
|
54
|
+
data = self._requester.request("POST", "/disk")
|
|
55
|
+
return Disk.model_validate(data)
|
|
56
|
+
|
|
57
|
+
def delete(self, disk_id: str) -> None:
|
|
58
|
+
"""Delete a disk by its ID.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
disk_id: The UUID of the disk to delete.
|
|
62
|
+
"""
|
|
63
|
+
self._requester.request("DELETE", f"/disk/{disk_id}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DiskArtifactsAPI:
|
|
67
|
+
def __init__(self, requester: RequesterProtocol) -> None:
|
|
68
|
+
self._requester = requester
|
|
69
|
+
|
|
70
|
+
def upsert(
|
|
71
|
+
self,
|
|
72
|
+
disk_id: str,
|
|
73
|
+
*,
|
|
74
|
+
file: FileUpload
|
|
75
|
+
| tuple[str, BinaryIO | bytes]
|
|
76
|
+
| tuple[str, BinaryIO | bytes, str | None],
|
|
77
|
+
file_path: str | None = None,
|
|
78
|
+
meta: Mapping[str, Any] | MutableMapping[str, Any] | None = None,
|
|
79
|
+
) -> Artifact:
|
|
80
|
+
"""Upload a file to create or update an artifact.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
disk_id: The UUID of the disk.
|
|
84
|
+
file: The file to upload (FileUpload object or tuple format).
|
|
85
|
+
file_path: Directory path (not including filename), defaults to "/".
|
|
86
|
+
meta: Custom metadata as JSON-serializable dict, defaults to None.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Artifact containing the created/updated artifact information.
|
|
90
|
+
"""
|
|
91
|
+
upload = normalize_file_upload(file)
|
|
92
|
+
files = {"file": upload.as_httpx()}
|
|
93
|
+
form: dict[str, Any] = {}
|
|
94
|
+
if file_path:
|
|
95
|
+
form["file_path"] = file_path
|
|
96
|
+
if meta is not None:
|
|
97
|
+
form["meta"] = json.dumps(cast(Mapping[str, Any], meta))
|
|
98
|
+
data = self._requester.request(
|
|
99
|
+
"POST",
|
|
100
|
+
f"/disk/{disk_id}/artifact",
|
|
101
|
+
data=form or None,
|
|
102
|
+
files=files,
|
|
103
|
+
)
|
|
104
|
+
return Artifact.model_validate(data)
|
|
105
|
+
|
|
106
|
+
def get(
|
|
107
|
+
self,
|
|
108
|
+
disk_id: str,
|
|
109
|
+
*,
|
|
110
|
+
file_path: str,
|
|
111
|
+
filename: str,
|
|
112
|
+
with_public_url: bool | None = None,
|
|
113
|
+
with_content: bool | None = None,
|
|
114
|
+
expire: int | None = None,
|
|
115
|
+
) -> GetArtifactResp:
|
|
116
|
+
"""Get an artifact by disk ID, file path, and filename.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
disk_id: The UUID of the disk.
|
|
120
|
+
file_path: Directory path (not including filename).
|
|
121
|
+
filename: The filename of the artifact.
|
|
122
|
+
with_public_url: Whether to include a presigned public URL. Defaults to None.
|
|
123
|
+
with_content: Whether to include file content. Defaults to None.
|
|
124
|
+
expire: URL expiration time in seconds. Defaults to None.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
GetArtifactResp containing the artifact and optionally public URL and content.
|
|
128
|
+
"""
|
|
129
|
+
full_path = f"{file_path.rstrip('/')}/{filename}"
|
|
130
|
+
params = build_params(
|
|
131
|
+
file_path=full_path,
|
|
132
|
+
with_public_url=with_public_url,
|
|
133
|
+
with_content=with_content,
|
|
134
|
+
expire=expire,
|
|
135
|
+
)
|
|
136
|
+
data = self._requester.request("GET", f"/disk/{disk_id}/artifact", params=params)
|
|
137
|
+
return GetArtifactResp.model_validate(data)
|
|
138
|
+
|
|
139
|
+
def update(
|
|
140
|
+
self,
|
|
141
|
+
disk_id: str,
|
|
142
|
+
*,
|
|
143
|
+
file_path: str,
|
|
144
|
+
filename: str,
|
|
145
|
+
meta: Mapping[str, Any] | MutableMapping[str, Any],
|
|
146
|
+
) -> UpdateArtifactResp:
|
|
147
|
+
"""Update an artifact's metadata.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
disk_id: The UUID of the disk.
|
|
151
|
+
file_path: Directory path (not including filename).
|
|
152
|
+
filename: The filename of the artifact.
|
|
153
|
+
meta: Custom metadata as JSON-serializable dict.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
UpdateArtifactResp containing the updated artifact information.
|
|
157
|
+
"""
|
|
158
|
+
full_path = f"{file_path.rstrip('/')}/{filename}"
|
|
159
|
+
payload = {
|
|
160
|
+
"file_path": full_path,
|
|
161
|
+
"meta": json.dumps(cast(Mapping[str, Any], meta)),
|
|
162
|
+
}
|
|
163
|
+
data = self._requester.request("PUT", f"/disk/{disk_id}/artifact", json_data=payload)
|
|
164
|
+
return UpdateArtifactResp.model_validate(data)
|
|
165
|
+
|
|
166
|
+
def delete(
|
|
167
|
+
self,
|
|
168
|
+
disk_id: str,
|
|
169
|
+
*,
|
|
170
|
+
file_path: str,
|
|
171
|
+
filename: str,
|
|
172
|
+
) -> None:
|
|
173
|
+
"""Delete an artifact by disk ID, file path, and filename.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
disk_id: The UUID of the disk.
|
|
177
|
+
file_path: Directory path (not including filename).
|
|
178
|
+
filename: The filename of the artifact.
|
|
179
|
+
"""
|
|
180
|
+
full_path = f"{file_path.rstrip('/')}/{filename}"
|
|
181
|
+
params = {"file_path": full_path}
|
|
182
|
+
self._requester.request("DELETE", f"/disk/{disk_id}/artifact", params=params)
|
|
183
|
+
|
|
184
|
+
def list(
|
|
185
|
+
self,
|
|
186
|
+
disk_id: str,
|
|
187
|
+
*,
|
|
188
|
+
path: str | None = None,
|
|
189
|
+
) -> ListArtifactsResp:
|
|
190
|
+
params: dict[str, Any] = {}
|
|
191
|
+
if path is not None:
|
|
192
|
+
params["path"] = path
|
|
193
|
+
data = self._requester.request("GET", f"/disk/{disk_id}/artifact/ls", params=params or None)
|
|
194
|
+
return ListArtifactsResp.model_validate(data)
|