basyx commited on
Commit
03c8ca2
·
verified ·
1 Parent(s): de27831

feat: resolve authenticated canonical asset inputs

Browse files
Files changed (1) hide show
  1. app/services/input_resolver.py +37 -19
app/services/input_resolver.py CHANGED
@@ -19,6 +19,8 @@ from app.services.cleanup import CleanupService, RequestWorkspace
19
  from app.services.downloader import Downloader
20
  from app.services.validator import MediaValidator
21
  from app.services.ytdlp_service import YTDLPService
 
 
22
 
23
 
24
  class InputResolver:
@@ -44,12 +46,14 @@ class InputResolver:
44
  downloader: Downloader,
45
  ytdlp: YTDLPService,
46
  validator: MediaValidator,
 
47
  ) -> None:
48
  self.settings = settings
49
  self.cleanup = cleanup
50
  self.downloader = downloader
51
  self.ytdlp = ytdlp
52
  self.validator = validator
 
53
 
54
  async def resolve(self, request: Request, *, require_input: bool = True) -> ResolvedRequest:
55
  request_id = request.state.request_id
@@ -257,6 +261,8 @@ class InputResolver:
257
  *,
258
  ytdlp_options: dict[str, Any] | None = None,
259
  ) -> InputMedia:
 
 
260
  if "temp_path" in descriptor or "path" in descriptor:
261
  return await self._resolve_local_path(descriptor, workspace)
262
  if "url" in descriptor:
@@ -323,6 +329,36 @@ class InputResolver:
323
  raise InputError("Input descriptor must contain a url or Base64 data")
324
  return await self._decode_base64(data, descriptor, workspace)
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  async def _resolve_local_path(
327
  self, descriptor: dict[str, Any], workspace: RequestWorkspace
328
  ) -> InputMedia:
@@ -447,22 +483,4 @@ class InputResolver:
447
  if isinstance(options, dict):
448
  params.update(options)
449
  for key, value in payload.items():
450
- if key not in self.INPUT_KEYS and not key.startswith("binary.") and key != "options":
451
- params[key] = self._parse_field(value)
452
- return params
453
-
454
- @staticmethod
455
- def _parse_field(value: Any) -> Any:
456
- if isinstance(value, list):
457
- return [InputResolver._parse_field(item) for item in value]
458
- if not isinstance(value, str):
459
- return value
460
- stripped = value.strip()
461
- if stripped.startswith(("{", "[")) or stripped in {"true", "false", "null"}:
462
- try:
463
- return json.loads(stripped)
464
- except json.JSONDecodeError:
465
- return value
466
- return value
467
-
468
- @staticm
 
19
  from app.services.downloader import Downloader
20
  from app.services.validator import MediaValidator
21
  from app.services.ytdlp_service import YTDLPService
22
+ from app.security.assets import CanonicalAssetNotFoundError, CanonicalAssetService
23
+ from app.security.context import auth_context
24
 
25
 
26
  class InputResolver:
 
46
  downloader: Downloader,
47
  ytdlp: YTDLPService,
48
  validator: MediaValidator,
49
+ assets: CanonicalAssetService | None = None,
50
  ) -> None:
51
  self.settings = settings
52
  self.cleanup = cleanup
53
  self.downloader = downloader
54
  self.ytdlp = ytdlp
55
  self.validator = validator
56
+ self.assets = assets
57
 
58
  async def resolve(self, request: Request, *, require_input: bool = True) -> ResolvedRequest:
59
  request_id = request.state.request_id
 
261
  *,
262
  ytdlp_options: dict[str, Any] | None = None,
263
  ) -> InputMedia:
264
+ if "asset_id" in descriptor:
265
+ return await self._resolve_asset(descriptor, workspace)
266
  if "temp_path" in descriptor or "path" in descriptor:
267
  return await self._resolve_local_path(descriptor, workspace)
268
  if "url" in descriptor:
 
329
  raise InputError("Input descriptor must contain a url or Base64 data")
330
  return await self._decode_base64(data, descriptor, workspace)
331
 
332
+ async def _resolve_asset(
333
+ self, descriptor: dict[str, Any], workspace: RequestWorkspace
334
+ ) -> InputMedia:
335
+ asset_id = descriptor.get("asset_id")
336
+ if not isinstance(asset_id, str) or not asset_id.strip():
337
+ raise InputError("asset_id must be a non-empty string")
338
+ context = auth_context.get()
339
+ if self.assets is None or context is None or not context.workspace_id or not context.user_id:
340
+ raise InputError("asset_id requires an authenticated workspace")
341
+ try:
342
+ record = await self.assets.get_owned_by_id(
343
+ workspace_id=context.workspace_id,
344
+ user_id=context.user_id,
345
+ asset_id=asset_id,
346
+ )
347
+ source = self.cleanup.resolve_download(record.request_id, record.filename)
348
+ await self.assets.verify_file(record, source)
349
+ except (CanonicalAssetNotFoundError, FileNotFoundError, ValueError) as exc:
350
+ raise InputError("Media asset was not found or is no longer available") from exc
351
+ destination = workspace.uploads / f"{uuid4().hex}_{record.filename}"
352
+ await asyncio.to_thread(source.replace, destination)
353
+ return InputMedia(
354
+ source=MediaSource.ASSET,
355
+ filename=record.filename,
356
+ mime_type=record.mime_type,
357
+ temp_path=destination,
358
+ size=record.file_size,
359
+ metadata={"asset_id": record.id, "sha256": record.sha256},
360
+ )
361
+
362
  async def _resolve_local_path(
363
  self, descriptor: dict[str, Any], workspace: RequestWorkspace
364
  ) -> InputMedia:
 
483
  if isinstance(options, dict):
484
  params.update(options)
485
  for key, value in payload.items():
486
+ if key not in self.INPUT_KEYS and not key.startswith("binary.") and key != "options":