global refactoring; add support for Assets without the computed hash

This commit is contained in:
bigcat88
2025-09-12 18:14:52 +03:00
parent 934377ac1e
commit bb9ed04758
27 changed files with 2380 additions and 1930 deletions

View File

@@ -1,7 +1,7 @@
import contextlib
import os
import uuid
import urllib.parse
import uuid
from typing import Optional
from aiohttp import web
@@ -12,7 +12,6 @@ import folder_paths
from .. import assets_manager, assets_scanner, user_manager
from . import schemas_in, schemas_out
ROUTES = web.RouteTableDef()
UserManager: Optional[user_manager.UserManager] = None
@@ -272,6 +271,7 @@ async def upload_asset(request: web.Request) -> web.Response:
temp_path=tmp_path,
client_filename=file_client_name,
owner_id=owner_id,
expected_asset_hash=spec.hash,
)
status = 201 if created.created_new else 200
return web.json_response(created.model_dump(mode="json"), status=status)
@@ -332,6 +332,29 @@ async def update_asset(request: web.Request) -> web.Response:
return web.json_response(result.model_dump(mode="json"), status=200)
@ROUTES.put(f"/api/assets/{{id:{UUID_RE}}}/preview")
async def set_asset_preview(request: web.Request) -> web.Response:
asset_info_id = str(uuid.UUID(request.match_info["id"]))
try:
body = schemas_in.SetPreviewBody.model_validate(await request.json())
except ValidationError as ve:
return _validation_error_response("INVALID_BODY", ve)
except Exception:
return _error_response(400, "INVALID_JSON", "Request body must be valid JSON.")
try:
result = await assets_manager.set_asset_preview(
asset_info_id=asset_info_id,
preview_asset_id=body.preview_id,
owner_id=UserManager.get_request_user_id(request),
)
except (PermissionError, ValueError) as ve:
return _error_response(404, "ASSET_NOT_FOUND", str(ve), {"id": asset_info_id})
except Exception:
return _error_response(500, "INTERNAL", "Unexpected server error.")
return web.json_response(result.model_dump(mode="json"), status=200)
@ROUTES.delete(f"/api/assets/{{id:{UUID_RE}}}")
async def delete_asset(request: web.Request) -> web.Response:
asset_info_id = str(uuid.UUID(request.match_info["id"]))

View File

@@ -1,7 +1,15 @@
import json
import uuid
from typing import Any, Literal, Optional
from typing import Any, Optional, Literal
from pydantic import BaseModel, Field, ConfigDict, field_validator, model_validator, conint
from pydantic import (
BaseModel,
ConfigDict,
Field,
conint,
field_validator,
model_validator,
)
class ListAssetsQuery(BaseModel):
@@ -148,30 +156,12 @@ class TagsRemove(TagsAdd):
pass
class ScheduleAssetScanBody(BaseModel):
roots: list[Literal["models","input","output"]] = Field(default_factory=list)
RootType = Literal["models", "input", "output"]
ALLOWED_ROOTS: tuple[RootType, ...] = ("models", "input", "output")
@field_validator("roots", mode="before")
@classmethod
def _normalize_roots(cls, v):
if v is None:
return []
if isinstance(v, str):
items = [x.strip().lower() for x in v.split(",")]
elif isinstance(v, list):
items = []
for x in v:
if isinstance(x, str):
items.extend([p.strip().lower() for p in x.split(",")])
else:
return []
out = []
seen = set()
for r in items:
if r in {"models","input","output"} and r not in seen:
out.append(r)
seen.add(r)
return out
class ScheduleAssetScanBody(BaseModel):
roots: list[RootType] = Field(..., min_length=1)
class UploadAssetSpec(BaseModel):
@@ -281,3 +271,22 @@ class UploadAssetSpec(BaseModel):
if len(self.tags) < 2:
raise ValueError("models uploads require a category tag as the second tag")
return self
class SetPreviewBody(BaseModel):
"""Set or clear the preview for an AssetInfo. Provide an Asset.id or null."""
preview_id: Optional[str] = None
@field_validator("preview_id", mode="before")
@classmethod
def _norm_uuid(cls, v):
if v is None:
return None
s = str(v).strip()
if not s:
return None
try:
uuid.UUID(s)
except Exception:
raise ValueError("preview_id must be a UUID")
return s

View File

@@ -1,12 +1,13 @@
from datetime import datetime
from typing import Any, Literal, Optional
from pydantic import BaseModel, ConfigDict, Field, field_serializer
class AssetSummary(BaseModel):
id: str
name: str
asset_hash: str
asset_hash: Optional[str]
size: Optional[int] = None
mime_type: Optional[str] = None
tags: list[str] = Field(default_factory=list)
@@ -31,7 +32,7 @@ class AssetsList(BaseModel):
class AssetUpdated(BaseModel):
id: str
name: str
asset_hash: str
asset_hash: Optional[str]
tags: list[str] = Field(default_factory=list)
user_metadata: dict[str, Any] = Field(default_factory=dict)
updated_at: Optional[datetime] = None
@@ -46,12 +47,12 @@ class AssetUpdated(BaseModel):
class AssetDetail(BaseModel):
id: str
name: str
asset_hash: str
asset_hash: Optional[str]
size: Optional[int] = None
mime_type: Optional[str] = None
tags: list[str] = Field(default_factory=list)
user_metadata: dict[str, Any] = Field(default_factory=dict)
preview_hash: Optional[str] = None
preview_id: Optional[str] = None
created_at: Optional[datetime] = None
last_access_time: Optional[datetime] = None
@@ -95,7 +96,6 @@ class TagsRemove(BaseModel):
class AssetScanError(BaseModel):
path: str
message: str
phase: Literal["fast", "slow"]
at: Optional[str] = Field(None, description="ISO timestamp")
@@ -108,8 +108,6 @@ class AssetScanStatus(BaseModel):
finished_at: Optional[str] = None
discovered: int = 0
processed: int = 0
slow_queue_total: int = 0
slow_queue_finished: int = 0
file_errors: list[AssetScanError] = Field(default_factory=list)