|
@@ -39,6 +39,7 @@ from backend.app.core.config import settings as app_settings
|
|
|
from backend.app.core.database import async_session, get_db
|
|
from backend.app.core.database import async_session, get_db
|
|
|
from backend.app.core.permissions import Permission
|
|
from backend.app.core.permissions import Permission
|
|
|
from backend.app.core.printer_scope import PrinterScope, ensure_model_target_allowed
|
|
from backend.app.core.printer_scope import PrinterScope, ensure_model_target_allowed
|
|
|
|
|
+from backend.app.core.row_visibility import visible_row_clauses
|
|
|
from backend.app.core.tasks import spawn_background_task
|
|
from backend.app.core.tasks import spawn_background_task
|
|
|
from backend.app.models.archive import PrintArchive
|
|
from backend.app.models.archive import PrintArchive
|
|
|
from backend.app.models.library import LibraryFile, LibraryFileTag, LibraryFolder
|
|
from backend.app.models.library import LibraryFile, LibraryFileTag, LibraryFolder
|
|
@@ -163,18 +164,24 @@ def _ensure_library_file_visible(
|
|
|
return library_file
|
|
return library_file
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def _linkable_archives(db: AsyncSession, archive_ids, printer_scope: PrinterScope) -> dict[int, str | None]:
|
|
|
|
|
- """Print names of the archives among ``archive_ids`` on the caller's printers.
|
|
|
|
|
|
|
+async def _linkable_archives(
|
|
|
|
|
+ db: AsyncSession, archive_ids, printer_scope: PrinterScope, user: User | ApiKeyActor | None
|
|
|
|
|
+) -> dict[int, str | None]:
|
|
|
|
|
+ """Print names of the archives among ``archive_ids`` the caller sees in the archives.
|
|
|
|
|
|
|
|
- A folder's link to any other archive reads as no link, and linking one
|
|
|
|
|
- answers as for a missing archive.
|
|
|
|
|
|
|
+ Live archives on the caller's printers, their own unless they see
|
|
|
|
|
+ everyone's. A folder's link to any other archive reads as no link, and
|
|
|
|
|
+ linking one answers as for a missing archive.
|
|
|
"""
|
|
"""
|
|
|
ids = {i for i in archive_ids if i}
|
|
ids = {i for i in archive_ids if i}
|
|
|
- if not ids:
|
|
|
|
|
|
|
+ visible = visible_row_clauses(
|
|
|
|
|
+ PrintArchive, printer_scope, user, Permission.ARCHIVES_READ_ALL, Permission.ARCHIVES_READ_OWN
|
|
|
|
|
+ )
|
|
|
|
|
+ if not ids or visible is None:
|
|
|
return {}
|
|
return {}
|
|
|
- query = select(PrintArchive.id, PrintArchive.print_name).where(PrintArchive.id.in_(ids))
|
|
|
|
|
- if (clause := printer_scope.where(PrintArchive.printer_id)) is not None:
|
|
|
|
|
- query = query.where(clause)
|
|
|
|
|
|
|
+ query = select(PrintArchive.id, PrintArchive.print_name).where(
|
|
|
|
|
+ PrintArchive.id.in_(ids), PrintArchive.deleted_at.is_(None), *visible
|
|
|
|
|
+ )
|
|
|
return dict((await db.execute(query)).all())
|
|
return dict((await db.execute(query)).all())
|
|
|
|
|
|
|
|
|
|
|
|
@@ -688,6 +695,7 @@ async def save_3mf_bytes_to_library(
|
|
|
source_type: str | None = None,
|
|
source_type: str | None = None,
|
|
|
source_url: str | None = None,
|
|
source_url: str | None = None,
|
|
|
owner_id: int | None = None,
|
|
owner_id: int | None = None,
|
|
|
|
|
+ dedupe_within: list | tuple | None = (),
|
|
|
) -> tuple[LibraryFile, bool]:
|
|
) -> tuple[LibraryFile, bool]:
|
|
|
"""Save a fetched file into the library and return ``(library_file, was_existing)``.
|
|
"""Save a fetched file into the library and return ``(library_file, was_existing)``.
|
|
|
|
|
|
|
@@ -697,14 +705,18 @@ async def save_3mf_bytes_to_library(
|
|
|
if a LibraryFile with the same source_url already exists, the existing
|
|
if a LibraryFile with the same source_url already exists, the existing
|
|
|
row is returned and the bytes are NOT re-saved (MakerWorld signed URLs
|
|
row is returned and the bytes are NOT re-saved (MakerWorld signed URLs
|
|
|
change each download, so hash-based dedupe alone would miss re-imports).
|
|
change each download, so hash-based dedupe alone would miss re-imports).
|
|
|
|
|
+ ``dedupe_within`` narrows the rows that count as that copy to the ones the
|
|
|
|
|
+ importer may open; None imports a copy of their own regardless.
|
|
|
|
|
|
|
|
Parses 3MF metadata + thumbnail the same way the multipart upload route
|
|
Parses 3MF metadata + thumbnail the same way the multipart upload route
|
|
|
does, via :class:`ThreeMFParser`, and renders an STL's thumbnail. Paths are stored as relative so the
|
|
does, via :class:`ThreeMFParser`, and renders an STL's thumbnail. Paths are stored as relative so the
|
|
|
library is portable across installs.
|
|
library is portable across installs.
|
|
|
"""
|
|
"""
|
|
|
# Source-URL-based dedupe: return the existing row untouched.
|
|
# Source-URL-based dedupe: return the existing row untouched.
|
|
|
- if source_url:
|
|
|
|
|
- existing = await db.execute(LibraryFile.active().where(LibraryFile.source_url == source_url).limit(1))
|
|
|
|
|
|
|
+ if source_url and dedupe_within is not None:
|
|
|
|
|
+ existing = await db.execute(
|
|
|
|
|
+ LibraryFile.active().where(LibraryFile.source_url == source_url, *dedupe_within).limit(1)
|
|
|
|
|
+ )
|
|
|
existing_row = existing.scalar_one_or_none()
|
|
existing_row = existing.scalar_one_or_none()
|
|
|
if existing_row is not None:
|
|
if existing_row is not None:
|
|
|
return existing_row, True
|
|
return existing_row, True
|
|
@@ -722,6 +734,26 @@ async def save_3mf_bytes_to_library(
|
|
|
folder_q = await db.execute(select(LibraryFolder).where(LibraryFolder.id == folder_id))
|
|
folder_q = await db.execute(select(LibraryFolder).where(LibraryFolder.id == folder_id))
|
|
|
target_folder = folder_q.scalar_one_or_none()
|
|
target_folder = folder_q.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
+ # Another user's copy of this very import, which the importer may not
|
|
|
|
|
+ # open, may already sit on the mount under this name: theirs is stored
|
|
|
|
|
+ # beside it under a free name, as a slice is. Any other file of that name
|
|
|
|
|
+ # is still refused, so nothing put there outside Bambuddy is touched.
|
|
|
|
|
+ if (
|
|
|
|
|
+ source_url
|
|
|
|
|
+ and target_folder is not None
|
|
|
|
|
+ and target_folder.is_external
|
|
|
|
|
+ and target_folder.external_path
|
|
|
|
|
+ and Path(target_folder.external_path).is_dir()
|
|
|
|
|
+ and await db.scalar(
|
|
|
|
|
+ select(LibraryFile.id)
|
|
|
|
|
+ .where(LibraryFile.source_url == source_url, LibraryFile.deleted_at.is_(None))
|
|
|
|
|
+ .limit(1)
|
|
|
|
|
+ )
|
|
|
|
|
+ is not None
|
|
|
|
|
+ ):
|
|
|
|
|
+ with contextlib.suppress(PathTraversalError):
|
|
|
|
|
+ filename = _unique_external_name(Path(target_folder.external_path), filename)
|
|
|
|
|
+
|
|
|
file_path, is_external = _resolve_upload_destination(target_folder, filename)
|
|
file_path, is_external = _resolve_upload_destination(target_folder, filename)
|
|
|
ext = file_path.suffix.lower() or ".3mf"
|
|
ext = file_path.suffix.lower() or ".3mf"
|
|
|
with open(file_path, "wb") as fh:
|
|
with open(file_path, "wb") as fh:
|
|
@@ -1029,6 +1061,7 @@ async def list_folders(
|
|
|
)
|
|
)
|
|
|
),
|
|
),
|
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
|
|
|
+ actor: User | ApiKeyActor | None = RequestActor,
|
|
|
):
|
|
):
|
|
|
"""Get all folders as a tree structure.
|
|
"""Get all folders as a tree structure.
|
|
|
|
|
|
|
@@ -1049,7 +1082,7 @@ async def list_folders(
|
|
|
.order_by(LibraryFolder.name)
|
|
.order_by(LibraryFolder.name)
|
|
|
)
|
|
)
|
|
|
rows = [row for row in result.all() if visible is None or row[0].id in visible]
|
|
rows = [row for row in result.all() if visible is None or row[0].id in visible]
|
|
|
- archives = await _linkable_archives(db, [row[0].archive_id for row in rows], printer_scope)
|
|
|
|
|
|
|
+ archives = await _linkable_archives(db, [row[0].archive_id for row in rows], printer_scope, actor)
|
|
|
own_files = _own_files_filter(user)
|
|
own_files = _own_files_filter(user)
|
|
|
|
|
|
|
|
# Get file counts per folder
|
|
# Get file counts per folder
|
|
@@ -1149,6 +1182,7 @@ async def get_folders_by_project(
|
|
|
)
|
|
)
|
|
|
),
|
|
),
|
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
|
|
|
+ actor: User | ApiKeyActor | None = RequestActor,
|
|
|
):
|
|
):
|
|
|
"""Get all folders linked to a specific project, those the user may see (#3201)."""
|
|
"""Get all folders linked to a specific project, those the user may see (#3201)."""
|
|
|
user, _ = auth_result
|
|
user, _ = auth_result
|
|
@@ -1162,7 +1196,7 @@ async def get_folders_by_project(
|
|
|
.order_by(LibraryFolder.name)
|
|
.order_by(LibraryFolder.name)
|
|
|
)
|
|
)
|
|
|
rows = [row for row in result.all() if visible is None or row[0].id in visible]
|
|
rows = [row for row in result.all() if visible is None or row[0].id in visible]
|
|
|
- archives = await _linkable_archives(db, [row[0].archive_id for row in rows], printer_scope)
|
|
|
|
|
|
|
+ archives = await _linkable_archives(db, [row[0].archive_id for row in rows], printer_scope, actor)
|
|
|
|
|
|
|
|
folders = []
|
|
folders = []
|
|
|
for folder, project_name in rows:
|
|
for folder, project_name in rows:
|
|
@@ -1217,21 +1251,27 @@ async def get_folders_by_archive(
|
|
|
)
|
|
)
|
|
|
),
|
|
),
|
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
|
|
|
+ actor: User | ApiKeyActor | None = RequestActor,
|
|
|
):
|
|
):
|
|
|
"""Get all folders linked to a specific archive, those the user may see (#3201)."""
|
|
"""Get all folders linked to a specific archive, those the user may see (#3201)."""
|
|
|
user, _ = auth_result
|
|
user, _ = auth_result
|
|
|
- return (await _folders_by_archive(db, user, [archive_id], printer_scope))[archive_id]
|
|
|
|
|
|
|
+ return (await _folders_by_archive(db, user, [archive_id], printer_scope, actor))[archive_id]
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _folders_by_archive(
|
|
async def _folders_by_archive(
|
|
|
- db: AsyncSession, user: User | ApiKeyActor | None, archive_ids: list[int], printer_scope: PrinterScope
|
|
|
|
|
|
|
+ db: AsyncSession,
|
|
|
|
|
+ user: User | ApiKeyActor | None,
|
|
|
|
|
+ archive_ids: list[int],
|
|
|
|
|
+ printer_scope: PrinterScope,
|
|
|
|
|
+ actor: User | ApiKeyActor | None,
|
|
|
) -> dict[int, list]:
|
|
) -> dict[int, list]:
|
|
|
"""The folders linked to each archive that *user* may see (#3201), by archive id.
|
|
"""The folders linked to each archive that *user* may see (#3201), by archive id.
|
|
|
|
|
|
|
|
- An archive on a printer outside *printer_scope* has none.
|
|
|
|
|
|
|
+ An archive *actor* (the caller, as ``RequestActor`` gives it) does not see
|
|
|
|
|
+ in the archives has none.
|
|
|
"""
|
|
"""
|
|
|
folders: dict[int, list] = {archive_id: [] for archive_id in archive_ids}
|
|
folders: dict[int, list] = {archive_id: [] for archive_id in archive_ids}
|
|
|
- archives = await _linkable_archives(db, archive_ids, printer_scope)
|
|
|
|
|
|
|
+ archives = await _linkable_archives(db, archive_ids, printer_scope, actor)
|
|
|
if not archives:
|
|
if not archives:
|
|
|
return folders
|
|
return folders
|
|
|
index = await _load_index(db, user)
|
|
index = await _load_index(db, user)
|
|
@@ -1308,7 +1348,7 @@ async def create_folder(
|
|
|
project_name = project.name
|
|
project_name = project.name
|
|
|
|
|
|
|
|
# Verify archive exists if specified
|
|
# Verify archive exists if specified
|
|
|
- archives = await _linkable_archives(db, [data.archive_id], printer_scope)
|
|
|
|
|
|
|
+ archives = await _linkable_archives(db, [data.archive_id], printer_scope, actor)
|
|
|
if data.archive_id is not None and data.archive_id not in archives:
|
|
if data.archive_id is not None and data.archive_id not in archives:
|
|
|
raise HTTPException(status_code=404, detail="Archive not found")
|
|
raise HTTPException(status_code=404, detail="Archive not found")
|
|
|
|
|
|
|
@@ -1358,6 +1398,7 @@ async def get_folder(
|
|
|
)
|
|
)
|
|
|
),
|
|
),
|
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
|
|
|
+ actor: User | ApiKeyActor | None = RequestActor,
|
|
|
):
|
|
):
|
|
|
"""Get a folder by ID; 404 for a folder the user may not see (#3201)."""
|
|
"""Get a folder by ID; 404 for a folder the user may not see (#3201)."""
|
|
|
user, _ = auth_result
|
|
user, _ = auth_result
|
|
@@ -1376,7 +1417,7 @@ async def get_folder(
|
|
|
raise HTTPException(status_code=404, detail="Folder not found")
|
|
raise HTTPException(status_code=404, detail="Folder not found")
|
|
|
|
|
|
|
|
folder, project_name = row
|
|
folder, project_name = row
|
|
|
- archives = await _linkable_archives(db, [folder.archive_id], printer_scope)
|
|
|
|
|
|
|
+ archives = await _linkable_archives(db, [folder.archive_id], printer_scope, actor)
|
|
|
|
|
|
|
|
# Get file count + latest file activity (#1770) in one trip
|
|
# Get file count + latest file activity (#1770) in one trip
|
|
|
agg_result = await db.execute(
|
|
agg_result = await db.execute(
|
|
@@ -1488,6 +1529,7 @@ async def update_folder(
|
|
|
)
|
|
)
|
|
|
),
|
|
),
|
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
printer_scope: PrinterScope = RequestPrinterScope,
|
|
|
|
|
+ actor: User | ApiKeyActor | None = RequestActor,
|
|
|
):
|
|
):
|
|
|
"""Update a folder.
|
|
"""Update a folder.
|
|
|
|
|
|
|
@@ -1548,11 +1590,11 @@ async def update_folder(
|
|
|
if data.archive_id is not None:
|
|
if data.archive_id is not None:
|
|
|
if data.archive_id == 0:
|
|
if data.archive_id == 0:
|
|
|
# A link the caller can't see is not theirs to remove.
|
|
# A link the caller can't see is not theirs to remove.
|
|
|
- if folder.archive_id in await _linkable_archives(db, [folder.archive_id], printer_scope):
|
|
|
|
|
|
|
+ if folder.archive_id in await _linkable_archives(db, [folder.archive_id], printer_scope, actor):
|
|
|
folder.archive_id = None
|
|
folder.archive_id = None
|
|
|
else:
|
|
else:
|
|
|
# Verify archive exists
|
|
# Verify archive exists
|
|
|
- if data.archive_id not in await _linkable_archives(db, [data.archive_id], printer_scope):
|
|
|
|
|
|
|
+ if data.archive_id not in await _linkable_archives(db, [data.archive_id], printer_scope, actor):
|
|
|
raise HTTPException(status_code=404, detail="Archive not found")
|
|
raise HTTPException(status_code=404, detail="Archive not found")
|
|
|
folder.archive_id = data.archive_id
|
|
folder.archive_id = data.archive_id
|
|
|
|
|
|
|
@@ -1579,7 +1621,7 @@ async def update_folder(
|
|
|
if folder.project_id:
|
|
if folder.project_id:
|
|
|
project_result = await db.execute(select(Project.name).where(Project.id == folder.project_id))
|
|
project_result = await db.execute(select(Project.name).where(Project.id == folder.project_id))
|
|
|
project_name = project_result.scalar()
|
|
project_name = project_result.scalar()
|
|
|
- archives = await _linkable_archives(db, [folder.archive_id], printer_scope)
|
|
|
|
|
|
|
+ archives = await _linkable_archives(db, [folder.archive_id], printer_scope, actor)
|
|
|
|
|
|
|
|
return FolderResponse(
|
|
return FolderResponse(
|
|
|
id=folder.id,
|
|
id=folder.id,
|
|
@@ -4850,6 +4892,10 @@ async def guard_nozzle_class_reslice(
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# slice_and_persist's default owner: the user it slices for.
|
|
|
|
|
+_CALLER = object()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
async def slice_and_persist(
|
|
async def slice_and_persist(
|
|
|
db: AsyncSession,
|
|
db: AsyncSession,
|
|
|
*,
|
|
*,
|
|
@@ -4860,10 +4906,14 @@ async def slice_and_persist(
|
|
|
request: SliceRequest,
|
|
request: SliceRequest,
|
|
|
current_user_id: int | None,
|
|
current_user_id: int | None,
|
|
|
job_id: int | None = None,
|
|
job_id: int | None = None,
|
|
|
|
|
+ owner_id: int | None | object = _CALLER,
|
|
|
) -> SliceResponse:
|
|
) -> SliceResponse:
|
|
|
"""Slice a model and save the result as a new ``LibraryFile`` in
|
|
"""Slice a model and save the result as a new ``LibraryFile`` in
|
|
|
``folder_id`` (same folder as the source by convention).
|
|
``folder_id`` (same folder as the source by convention).
|
|
|
|
|
|
|
|
|
|
+ ``current_user_id`` resolves cloud presets; the new file is ``owner_id``'s,
|
|
|
|
|
+ by default the same user.
|
|
|
|
|
+
|
|
|
Always exports as ``.gcode.3mf`` so the existing library thumbnail
|
|
Always exports as ``.gcode.3mf`` so the existing library thumbnail
|
|
|
pipeline works on the new file. Plain ``.gcode`` would have no
|
|
pipeline works on the new file. Plain ``.gcode`` would have no
|
|
|
embedded thumbnail to extract.
|
|
embedded thumbnail to extract.
|
|
@@ -4965,7 +5015,7 @@ async def slice_and_persist(
|
|
|
from backend.app.services.slice_dispatch import slice_dispatch as _dispatch
|
|
from backend.app.services.slice_dispatch import slice_dispatch as _dispatch
|
|
|
|
|
|
|
|
# Nobody's, when the user was deleted while it sliced: the id may be someone else's now.
|
|
# Nobody's, when the user was deleted while it sliced: the id may be someone else's now.
|
|
|
- current_user_id = _dispatch.owner_now(job_id, current_user_id)
|
|
|
|
|
|
|
+ file_owner_id = _dispatch.owner_now(job_id, current_user_id if owner_id is _CALLER else owner_id) # type: ignore[arg-type]
|
|
|
new_file = LibraryFile(
|
|
new_file = LibraryFile(
|
|
|
folder_id=folder_id,
|
|
folder_id=folder_id,
|
|
|
is_external=out_is_external,
|
|
is_external=out_is_external,
|
|
@@ -4983,7 +5033,7 @@ async def slice_and_persist(
|
|
|
thumbnail_path=thumbnail_relative,
|
|
thumbnail_path=thumbnail_relative,
|
|
|
file_metadata=metadata,
|
|
file_metadata=metadata,
|
|
|
source_type="sliced",
|
|
source_type="sliced",
|
|
|
- created_by_id=current_user_id,
|
|
|
|
|
|
|
+ created_by_id=file_owner_id,
|
|
|
)
|
|
)
|
|
|
db.add(new_file)
|
|
db.add(new_file)
|
|
|
await db.commit()
|
|
await db.commit()
|
|
@@ -5011,6 +5061,7 @@ async def slice_and_persist_as_archive(
|
|
|
source_archive, # PrintArchive — hint kept loose to avoid cyclic import
|
|
source_archive, # PrintArchive — hint kept loose to avoid cyclic import
|
|
|
current_user_id: int | None,
|
|
current_user_id: int | None,
|
|
|
job_id: int | None = None,
|
|
job_id: int | None = None,
|
|
|
|
|
+ owner_id: int | None | object = _CALLER,
|
|
|
):
|
|
):
|
|
|
"""Slice a model and save the result as a new ``PrintArchive`` row,
|
|
"""Slice a model and save the result as a new ``PrintArchive`` row,
|
|
|
inheriting printer / project / makerworld metadata from the source
|
|
inheriting printer / project / makerworld metadata from the source
|
|
@@ -5154,7 +5205,7 @@ async def slice_and_persist_as_archive(
|
|
|
from backend.app.services.slice_dispatch import slice_dispatch as _dispatch
|
|
from backend.app.services.slice_dispatch import slice_dispatch as _dispatch
|
|
|
|
|
|
|
|
# Nobody's, when the user was deleted while it sliced: the id may be someone else's now.
|
|
# Nobody's, when the user was deleted while it sliced: the id may be someone else's now.
|
|
|
- current_user_id = _dispatch.owner_now(job_id, current_user_id)
|
|
|
|
|
|
|
+ current_user_id = _dispatch.owner_now(job_id, current_user_id if owner_id is _CALLER else owner_id) # type: ignore[arg-type]
|
|
|
new_archive = PrintArchive(
|
|
new_archive = PrintArchive(
|
|
|
printer_id=new_printer_id,
|
|
printer_id=new_printer_id,
|
|
|
project_id=source_archive.project_id,
|
|
project_id=source_archive.project_id,
|
|
@@ -5275,6 +5326,9 @@ async def slice_library_file(
|
|
|
# cloud_token (#1182 follow-up).
|
|
# cloud_token (#1182 follow-up).
|
|
|
cloud_token_user = current_user or api_key_cloud_owner
|
|
cloud_token_user = current_user or api_key_cloud_owner
|
|
|
user_id = cloud_token_user.id if cloud_token_user else None
|
|
user_id = cloud_token_user.id if cloud_token_user else None
|
|
|
|
|
+ # The sliced file is its owner's also when no one resolves cloud presets,
|
|
|
|
|
+ # as a pipeline run's is.
|
|
|
|
|
+ owner_id = user_id if user_id is not None else (actor.id if isinstance(actor, ApiKeyActor) else None)
|
|
|
|
|
|
|
|
# If the source has a `print_name` in its metadata (BambuStudio always
|
|
# If the source has a `print_name` in its metadata (BambuStudio always
|
|
|
# sets this; OrcaSlicer often leaves it blank), derive the sliced
|
|
# sets this; OrcaSlicer often leaves it blank), derive the sliced
|
|
@@ -5312,6 +5366,7 @@ async def slice_library_file(
|
|
|
request=request,
|
|
request=request,
|
|
|
current_user_id=user_id,
|
|
current_user_id=user_id,
|
|
|
job_id=job_id,
|
|
job_id=job_id,
|
|
|
|
|
+ owner_id=owner_id,
|
|
|
)
|
|
)
|
|
|
except HTTPException as exc:
|
|
except HTTPException as exc:
|
|
|
raise http_exception_to_job_error(exc) from exc
|
|
raise http_exception_to_job_error(exc) from exc
|
|
@@ -5321,7 +5376,7 @@ async def slice_library_file(
|
|
|
kind="library_file",
|
|
kind="library_file",
|
|
|
source_id=lib_file.id,
|
|
source_id=lib_file.id,
|
|
|
source_name=lib_file.filename,
|
|
source_name=lib_file.filename,
|
|
|
- owner_id=user_id,
|
|
|
|
|
|
|
+ owner_id=owner_id,
|
|
|
run=_run,
|
|
run=_run,
|
|
|
)
|
|
)
|
|
|
return {
|
|
return {
|