update: update storage struct

This commit is contained in:
tao.chen
2026-08-13 11:01:37 +08:00
parent 7b10f08484
commit 49c8a27841
3 changed files with 38 additions and 30 deletions
+36 -28
View File
@@ -395,9 +395,14 @@ async def create_script_record(
# the legacy flow no longer applies. We still do a Scripts-only
# conflict check on the user-supplied name so two scripts cannot
# claim the same display name within the same workspace.
user_id = context.user.user_id
script_id = new_ulid()
jupyter_name = _jupyter_path(script_type, script_id)
nested_jupyter_name = f"{parent_ulid}/{jupyter_name}" if parent_ulid else jupyter_name
jupyter_basename = _jupyter_path(script_type, script_id)
# Jupyter path inside workspace root: {user_id}/{parent_or_self}/{basename}
if parent_ulid:
jupyter_path = f"{user_id}/{parent_ulid}/{jupyter_basename}"
else:
jupyter_path = f"{user_id}/{jupyter_basename}"
name_clash = await session.scalar(
select(Scripts.script_id).where(
Scripts.workspace_id == context.workspace.workspace_id,
@@ -421,7 +426,7 @@ async def create_script_record(
size_bytes = len(content)
if parent_ulid:
try:
await runtime_client.ensure_directory(workspace_id, parent_ulid)
await runtime_client.ensure_directory(workspace_id, f"{user_id}/{parent_ulid}")
except RuntimeClientError as exc:
raise HTTPException(
status_code=exc.status_code,
@@ -434,15 +439,15 @@ async def create_script_record(
logger.debug(notebook)
jupyter_resp = await runtime_client.create_notebook(
workspace_id,
name=nested_jupyter_name,
name=jupyter_path,
cells=notebook.get("cells"),
)
else:
jupyter_resp = await runtime_client.upload_file(
workspace_id,
name=nested_jupyter_name,
name=jupyter_path,
content=content.decode("utf-8"),
content_type=(mimetypes.guess_type(jupyter_name)[0] or "text/plain"),
content_type=(mimetypes.guess_type(jupyter_basename)[0] or "text/plain"),
)
logger.debug(jupyter_resp)
except RuntimeClientError as exc:
@@ -458,12 +463,12 @@ async def create_script_record(
# file is queryable as a workspace file from the user's POV; the
# storage_uri points at where the replicated bytes will land.
object_id = new_ulid()
object_key = f"{workspace_id}/{nested_jupyter_name}"
object_key = f"{workspace_id}/{jupyter_path}"
bucket_name = settings.s3_workspace_bucket
relative_path = user_relative_path(
context, f"{parent}/{jupyter_name}" if parent else jupyter_name
context, f"{parent}/{jupyter_basename}" if parent else jupyter_basename
)
mime_type = mimetypes.guess_type(jupyter_name)[0]
mime_type = mimetypes.guess_type(jupyter_basename)[0]
storage_object = StorageObjects(
storage_object_id=object_id,
workspace_id=context.workspace.workspace_id,
@@ -476,7 +481,7 @@ async def create_script_record(
object_key_hash=hashlib.sha256(object_key.encode("utf-8")).digest(),
storage_uri=build_storage_uri(bucket_name, object_key),
file_name=name,
file_extension=PurePosixPath(jupyter_name).suffix.lower() or None,
file_extension=PurePosixPath(jupyter_basename).suffix.lower() or None,
mime_type=mime_type,
size_bytes=size_bytes,
content_hash=content_hash,
@@ -507,13 +512,13 @@ async def create_script_record(
# Best-effort compensating cleanup: remove the Jupyter file we
# just created so a failed flush does not leave an orphan on disk.
try:
await runtime_client.delete_file(workspace_id, name=nested_jupyter_name)
await runtime_client.delete_file(workspace_id, name=jupyter_path)
except RuntimeClientError as cleanup_exc:
if cleanup_exc.status_code == 404:
pass
else:
logger.warning(
f"failed to clean up Jupyter file {nested_jupyter_name} "
f"failed to clean up Jupyter file {jupyter_path} "
f"after DB flush error: {cleanup_exc.status_code} {cleanup_exc.detail}"
)
raise
@@ -813,13 +818,17 @@ async def create_workspace_directory(
# the on-disk directory name match. Create the directory in Jupyter
# before persisting the DB row; if persistence fails we clean up.
dir_id = new_ulid()
jupyter_path = f"{parent_ulid}/{dir_id}" if parent_ulid else dir_id
user_id = context.user.user_id
if parent_ulid:
jupyter_path = f"{user_id}/{parent_ulid}/{dir_id}"
else:
jupyter_path = f"{user_id}/{dir_id}"
runtime_client = request.app.state.runtime_client
workspace_id = context.workspace.workspace_id
if parent_ulid:
try:
await runtime_client.ensure_directory(workspace_id, parent_ulid)
await runtime_client.ensure_directory(workspace_id, f"{user_id}/{parent_ulid}")
except RuntimeClientError as exc:
raise HTTPException(
status_code=exc.status_code,
@@ -962,7 +971,7 @@ async def delete_workspace_directory(
else:
try:
await runtime_client.delete_directory(
workspace_id, name=descendant.storage_object_id
workspace_id, name=f"{descendant.owner_user_id}/{descendant.storage_object_id}"
)
except RuntimeClientError as exc:
if exc.status_code != 404:
@@ -976,7 +985,7 @@ async def delete_workspace_directory(
descendant.deleted_at = datetime.now(UTC).replace(tzinfo=None)
try:
await runtime_client.delete_directory(workspace_id, name=target_ulid)
await runtime_client.delete_directory(workspace_id, name=f"{context.user.user_id}/{target_ulid}")
except RuntimeClientError as exc:
if exc.status_code != 404:
logger.warning(
@@ -1044,26 +1053,25 @@ async def get_script_content(
适用于被锁定的脚本,非所有者只能查看内容,不能编辑。
该接口不检查锁状态,调用方需自行判断权限。
"""
script = await session.scalar(
select(Scripts)
.where(
Scripts.script_id == script_id,
Scripts.workspace_id == context.workspace.workspace_id,
Scripts.status == "active",
)
script, storage_object = await get_script_row(
script_id,
context,
session,
allow_missing_storage_object=True,
)
if script is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, "script not found")
# 获取 Jupyter 路径
jupyter_name = _jupyter_path(script.script_type, script.script_id)
workspace_id = context.workspace.workspace_id
jupyter_path = _derive_jupyter_path(
storage_object, workspace_id, script.script_type, script.script_id
)
# 从 Jupyter 读取内容
runtime_client = request.app.state.runtime_client
try:
content_data = await runtime_client.get_file(
context.workspace.workspace_id,
name=jupyter_name,
workspace_id,
name=jupyter_path,
)
except RuntimeClientError as exc:
raise HTTPException(
+1 -1
View File
@@ -171,7 +171,7 @@ async def create_upload_record(
# Jupyter selects its editor from this suffix, so an extensionless
# object would make notebooks look like generic JSON/text files.
file_extension = PurePosixPath(_safe_file_name(payload.file_name)).suffix.lower()
object_key = f"{payload.workspace_id}/{new_ulid()}{file_extension}"
object_key = f"{payload.workspace_id}/{payload.user_id}/{new_ulid()}{file_extension}"
upload = UploadSessions(
upload_id=new_ulid(),
workspace_id=payload.workspace_id,
+1 -1
View File
@@ -190,7 +190,7 @@ async def create_upload_record(
# Jupyter selects its editor from this suffix, so an extensionless
# object would make notebooks look like generic JSON/text files.
file_extension = PurePosixPath(safe_file_name(payload.file_name)).suffix.lower()
object_key = f"{payload.workspace_id}/{upload_id}{file_extension}"
object_key = f"{payload.workspace_id}/{payload.user_id}/{upload_id}{file_extension}"
upload = UploadSessions(
upload_id=upload_id,
workspace_id=payload.workspace_id,