Skip to content

Commit 5d5d91e

Browse files
committed
docs(examples): add end-to-end workspace_id demo
补一个端到端 demo,演示新版 SDK 如何在 Credential 和 Sandbox Template 上 使用 workspace_id(PR #94 引入的字段)。 覆盖: - 显式传 workspace_id 创建资源(Credential、Template) - get 接口回读 workspace_id 并断言 - list 接口按 workspace_id 过滤并断言列表项命中 - finally 自动清理资源 环境变量: - AGENTRUN_ACCESS_KEY_ID / AGENTRUN_ACCESS_KEY_SECRET / AGENTRUN_REGION_ID - AGENTRUN_WORKSPACE_ID(必填,缺失即 SystemExit,避免误用默认 workspace) 真机验证: 本地用 workspace congxiao-wk (a42da758-e743-4880-99e3-3afab8287ebf) 跑通两轮,5.9s 完成所有 create→get→list→delete 流程,每步 workspace_id 都正确传递、回读、过滤生效,资源也自动清理。 mypy 通过;isort/pyink 与仓内其他 examples 一致存在 import 排序冲突, 属于 pre-existing 问题,不在本 PR 范围内。 Change-Id: I7f787abd5c3e91a8e5986e27258b6b683c0992b0 Co-developed-by: Claude <noreply@anthropic.com>
1 parent 333f548 commit 5d5d91e

1 file changed

Lines changed: 211 additions & 0 deletions

File tree

examples/workspace_id_demo.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
"""指定 workspace_id 创建 / 查询 / 过滤资源的端到端 Demo.
2+
3+
End-to-end demo: create / get / list resources with an explicit workspace_id.
4+
5+
本 demo 演示新版 SDK 在 Credential 和 Sandbox Template 两类资源上
6+
对 workspace_id 字段的支持:
7+
- 创建资源时显式指定 workspace_id,资源会落在该工作空间下
8+
- get 接口能回读 workspace_id
9+
- list 接口能按 workspace_id 过滤
10+
11+
Demonstrates how the new SDK exposes ``workspace_id`` on resource creation,
12+
read-back, and list-filter for both Credential and Sandbox Template.
13+
14+
环境变量 / Environment variables:
15+
- ``AGENTRUN_ACCESS_KEY_ID`` / ``AGENTRUN_ACCESS_KEY_SECRET``: AccessKey 凭据
16+
- ``AGENTRUN_REGION_ID``: 地域 (默认 cn-hangzhou)
17+
- ``AGENTRUN_WORKSPACE_ID``: 目标工作空间 ID。未配置时本 demo 直接报错退出,
18+
防止资源被误创建在默认工作空间。
19+
"""
20+
21+
import os
22+
import time
23+
import uuid
24+
25+
from agentrun.credential import (
26+
Credential,
27+
CredentialClient,
28+
CredentialConfig,
29+
CredentialCreateInput,
30+
CredentialListInput,
31+
)
32+
from agentrun.sandbox import Template
33+
from agentrun.sandbox.model import PageableInput, TemplateInput, TemplateType
34+
from agentrun.utils.exception import ResourceNotExistError
35+
from agentrun.utils.log import logger
36+
37+
38+
def _require_workspace_id() -> str:
39+
"""读取 workspace_id 环境变量,缺失则直接报错退出,避免误用默认工作空间."""
40+
workspace_id = os.getenv("AGENTRUN_WORKSPACE_ID")
41+
if not workspace_id:
42+
raise SystemExit(
43+
"AGENTRUN_WORKSPACE_ID is required for this demo. "
44+
"Set it to a real workspace ID before running."
45+
)
46+
return workspace_id
47+
48+
49+
def demo_credential_with_workspace(workspace_id: str) -> None:
50+
"""凭证:在指定 workspace_id 下创建 → 回读 → 列举过滤 → 清理."""
51+
logger.info("=" * 60)
52+
logger.info("Credential demo · workspace_id=%s", workspace_id)
53+
logger.info("=" * 60)
54+
55+
client = CredentialClient()
56+
suffix = uuid.uuid4().hex[:8]
57+
credential_name = f"sdk-ws-demo-cred-{suffix}"
58+
59+
cred: Credential | None = None
60+
try:
61+
# 1. 创建带 workspace_id 的凭证
62+
cred = Credential.create(
63+
CredentialCreateInput(
64+
credential_name=credential_name,
65+
description="workspace_id demo (safe to delete)",
66+
credential_config=CredentialConfig.inbound_api_key(
67+
"sk-demo-workspace-id"
68+
),
69+
workspace_id=workspace_id,
70+
)
71+
)
72+
logger.info("✓ created credential: %s", cred.credential_name)
73+
logger.info(
74+
" workspace_id (from create response): %s", cred.workspace_id
75+
)
76+
assert cred.workspace_id == workspace_id, (
77+
f"workspace_id mismatch on create: expected={workspace_id!r}, "
78+
f"got={cred.workspace_id!r}"
79+
)
80+
81+
# 2. get 接口回读 workspace_id
82+
cred_fetched = client.get(credential_name=credential_name)
83+
logger.info(
84+
"✓ get returned workspace_id: %s", cred_fetched.workspace_id
85+
)
86+
assert cred_fetched.workspace_id == workspace_id, (
87+
f"workspace_id mismatch on get: expected={workspace_id!r}, "
88+
f"got={cred_fetched.workspace_id!r}"
89+
)
90+
91+
# 3. list 按 workspace_id 过滤
92+
results = client.list(CredentialListInput(workspace_id=workspace_id))
93+
names = [item.credential_name for item in results]
94+
logger.info(
95+
"✓ list(workspace_id=%s) returned %d items",
96+
workspace_id,
97+
len(names),
98+
)
99+
assert credential_name in names, (
100+
f"credential {credential_name!r} not found in workspace-filtered"
101+
f" list; got {names!r}"
102+
)
103+
match = next(
104+
(
105+
item
106+
for item in results
107+
if item.credential_name == credential_name
108+
),
109+
None,
110+
)
111+
assert match is not None
112+
assert match.workspace_id == workspace_id, (
113+
f"list item workspace_id mismatch: expected={workspace_id!r}, "
114+
f"got={match.workspace_id!r}"
115+
)
116+
logger.info("✓ workspace_id round-trip verified for credential")
117+
finally:
118+
if cred is not None:
119+
try:
120+
cred.delete()
121+
logger.info("✓ cleaned up credential: %s", credential_name)
122+
except ResourceNotExistError:
123+
pass
124+
125+
126+
def demo_template_with_workspace(workspace_id: str) -> None:
127+
"""Sandbox Template:在指定 workspace_id 下创建 → 回读 → 列举过滤 → 清理."""
128+
logger.info("=" * 60)
129+
logger.info("Sandbox Template demo · workspace_id=%s", workspace_id)
130+
logger.info("=" * 60)
131+
132+
suffix = uuid.uuid4().hex[:8]
133+
template_name = f"sdk-ws-demo-tpl-{suffix}"
134+
135+
template: Template | None = None
136+
try:
137+
# 1. 创建带 workspace_id 的 Template
138+
template = Template.create(
139+
TemplateInput(
140+
template_name=template_name,
141+
template_type=TemplateType.CODE_INTERPRETER,
142+
description="workspace_id demo (safe to delete)",
143+
cpu=2.0,
144+
memory=4096,
145+
disk_size=512,
146+
sandbox_idle_timeout_in_seconds=600,
147+
sandbox_ttlin_seconds=600,
148+
workspace_id=workspace_id,
149+
)
150+
)
151+
logger.info("✓ created template: %s", template.template_name)
152+
logger.info(
153+
" workspace_id (from create response): %s", template.workspace_id
154+
)
155+
assert template.workspace_id == workspace_id, (
156+
f"workspace_id mismatch on create: expected={workspace_id!r}, "
157+
f"got={template.workspace_id!r}"
158+
)
159+
160+
# 2. get 接口回读 workspace_id
161+
fetched = Template.get_by_name(template_name)
162+
logger.info("✓ get returned workspace_id: %s", fetched.workspace_id)
163+
assert fetched.workspace_id == workspace_id, (
164+
f"workspace_id mismatch on get: expected={workspace_id!r}, "
165+
f"got={fetched.workspace_id!r}"
166+
)
167+
168+
# 3. list 按 workspace_id 过滤
169+
results = Template.list_templates(
170+
PageableInput(workspace_id=workspace_id, page_size=100)
171+
)
172+
names = [t.template_name for t in results or []]
173+
logger.info(
174+
"✓ list_templates(workspace_id=%s) returned %d items",
175+
workspace_id,
176+
len(names),
177+
)
178+
assert template_name in names, (
179+
f"template {template_name!r} not found in workspace-filtered list; "
180+
f"got {names!r}"
181+
)
182+
logger.info("✓ workspace_id round-trip verified for template")
183+
finally:
184+
if template is not None:
185+
try:
186+
Template.delete_by_name(template_name)
187+
logger.info("✓ cleaned up template: %s", template_name)
188+
except ResourceNotExistError:
189+
pass
190+
191+
192+
def main() -> None:
193+
workspace_id = _require_workspace_id()
194+
logger.info(
195+
"Running workspace_id demo · region=%s · ws=%s",
196+
os.getenv("AGENTRUN_REGION_ID", "cn-hangzhou"),
197+
workspace_id,
198+
)
199+
200+
started_at = time.time()
201+
demo_credential_with_workspace(workspace_id)
202+
demo_template_with_workspace(workspace_id)
203+
logger.info("=" * 60)
204+
logger.info(
205+
"✓ All workspace_id demos passed in %.1fs", time.time() - started_at
206+
)
207+
logger.info("=" * 60)
208+
209+
210+
if __name__ == "__main__":
211+
main()

0 commit comments

Comments
 (0)