mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-08 04:22:36 +08:00
Finished @ROUTES.post(f"/api/assets/{{id:{UUID_RE}}}/tags")
This commit is contained in:
parent
63c98d0c75
commit
9e3f559189
@ -93,32 +93,6 @@ async def get_asset(request: web.Request) -> web.Response:
|
|||||||
return web.json_response(result.model_dump(mode="json"), status=200)
|
return web.json_response(result.model_dump(mode="json"), status=200)
|
||||||
|
|
||||||
|
|
||||||
@ROUTES.get("/api/tags")
|
|
||||||
async def get_tags(request: web.Request) -> web.Response:
|
|
||||||
"""
|
|
||||||
GET request to list all tags based on query parameters.
|
|
||||||
"""
|
|
||||||
query_map = dict(request.rel_url.query)
|
|
||||||
|
|
||||||
try:
|
|
||||||
query = schemas_in.TagsListQuery.model_validate(query_map)
|
|
||||||
except ValidationError as e:
|
|
||||||
return web.json_response(
|
|
||||||
{"error": {"code": "INVALID_QUERY", "message": "Invalid query parameters", "details": e.errors()}},
|
|
||||||
status=400,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = manager.list_tags(
|
|
||||||
prefix=query.prefix,
|
|
||||||
limit=query.limit,
|
|
||||||
offset=query.offset,
|
|
||||||
order=query.order,
|
|
||||||
include_zero=query.include_zero,
|
|
||||||
owner_id=USER_MANAGER.get_request_user_id(request),
|
|
||||||
)
|
|
||||||
return web.json_response(result.model_dump(mode="json"))
|
|
||||||
|
|
||||||
|
|
||||||
@ROUTES.get(f"/api/assets/{{id:{UUID_RE}}}/content")
|
@ROUTES.get(f"/api/assets/{{id:{UUID_RE}}}/content")
|
||||||
async def download_asset_content(request: web.Request) -> web.Response:
|
async def download_asset_content(request: web.Request) -> web.Response:
|
||||||
# question: do we need disposition? could we just stick with one of these?
|
# question: do we need disposition? could we just stick with one of these?
|
||||||
@ -432,3 +406,59 @@ async def delete_asset(request: web.Request) -> web.Response:
|
|||||||
if not deleted:
|
if not deleted:
|
||||||
return _error_response(404, "ASSET_NOT_FOUND", f"AssetInfo {asset_info_id} not found.")
|
return _error_response(404, "ASSET_NOT_FOUND", f"AssetInfo {asset_info_id} not found.")
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
|
|
||||||
|
@ROUTES.get("/api/tags")
|
||||||
|
async def get_tags(request: web.Request) -> web.Response:
|
||||||
|
"""
|
||||||
|
GET request to list all tags based on query parameters.
|
||||||
|
"""
|
||||||
|
query_map = dict(request.rel_url.query)
|
||||||
|
|
||||||
|
try:
|
||||||
|
query = schemas_in.TagsListQuery.model_validate(query_map)
|
||||||
|
except ValidationError as e:
|
||||||
|
return web.json_response(
|
||||||
|
{"error": {"code": "INVALID_QUERY", "message": "Invalid query parameters", "details": e.errors()}},
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = manager.list_tags(
|
||||||
|
prefix=query.prefix,
|
||||||
|
limit=query.limit,
|
||||||
|
offset=query.offset,
|
||||||
|
order=query.order,
|
||||||
|
include_zero=query.include_zero,
|
||||||
|
owner_id=USER_MANAGER.get_request_user_id(request),
|
||||||
|
)
|
||||||
|
return web.json_response(result.model_dump(mode="json"))
|
||||||
|
|
||||||
|
@ROUTES.post(f"/api/assets/{{id:{UUID_RE}}}/tags")
|
||||||
|
async def add_asset_tags(request: web.Request) -> web.Response:
|
||||||
|
asset_info_id = str(uuid.UUID(request.match_info["id"]))
|
||||||
|
try:
|
||||||
|
payload = await request.json()
|
||||||
|
data = schemas_in.TagsAdd.model_validate(payload)
|
||||||
|
except ValidationError as ve:
|
||||||
|
return _error_response(400, "INVALID_BODY", "Invalid JSON body for tags add.", {"errors": ve.errors()})
|
||||||
|
except Exception:
|
||||||
|
return _error_response(400, "INVALID_JSON", "Request body must be valid JSON.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = manager.add_tags_to_asset(
|
||||||
|
asset_info_id=asset_info_id,
|
||||||
|
tags=data.tags,
|
||||||
|
origin="manual",
|
||||||
|
owner_id=USER_MANAGER.get_request_user_id(request),
|
||||||
|
)
|
||||||
|
except (ValueError, PermissionError) as ve:
|
||||||
|
return _error_response(404, "ASSET_NOT_FOUND", str(ve), {"id": asset_info_id})
|
||||||
|
except Exception:
|
||||||
|
logging.exception(
|
||||||
|
"add_tags_to_asset failed for asset_info_id=%s, owner_id=%s",
|
||||||
|
asset_info_id,
|
||||||
|
USER_MANAGER.get_request_user_id(request),
|
||||||
|
)
|
||||||
|
return _error_response(500, "INTERNAL", "Unexpected server error.")
|
||||||
|
|
||||||
|
return web.json_response(result.model_dump(mode="json"), status=200)
|
||||||
|
|||||||
@ -131,6 +131,29 @@ class TagsListQuery(BaseModel):
|
|||||||
return v.lower() or None
|
return v.lower() or None
|
||||||
|
|
||||||
|
|
||||||
|
class TagsAdd(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="ignore")
|
||||||
|
tags: list[str] = Field(..., min_length=1)
|
||||||
|
|
||||||
|
@field_validator("tags")
|
||||||
|
@classmethod
|
||||||
|
def normalize_tags(cls, v: list[str]) -> list[str]:
|
||||||
|
out = []
|
||||||
|
for t in v:
|
||||||
|
if not isinstance(t, str):
|
||||||
|
raise TypeError("tags must be strings")
|
||||||
|
tnorm = t.strip().lower()
|
||||||
|
if tnorm:
|
||||||
|
out.append(tnorm)
|
||||||
|
seen = set()
|
||||||
|
deduplicated = []
|
||||||
|
for x in out:
|
||||||
|
if x not in seen:
|
||||||
|
seen.add(x)
|
||||||
|
deduplicated.append(x)
|
||||||
|
return deduplicated
|
||||||
|
|
||||||
|
|
||||||
class UploadAssetSpec(BaseModel):
|
class UploadAssetSpec(BaseModel):
|
||||||
"""Upload Asset operation.
|
"""Upload Asset operation.
|
||||||
- tags: ordered; first is root ('models'|'input'|'output');
|
- tags: ordered; first is root ('models'|'input'|'output');
|
||||||
|
|||||||
@ -77,3 +77,10 @@ class TagsList(BaseModel):
|
|||||||
tags: list[TagUsage] = Field(default_factory=list)
|
tags: list[TagUsage] = Field(default_factory=list)
|
||||||
total: int
|
total: int
|
||||||
has_more: bool
|
has_more: bool
|
||||||
|
|
||||||
|
|
||||||
|
class TagsAdd(BaseModel):
|
||||||
|
model_config = ConfigDict(str_strip_whitespace=True)
|
||||||
|
added: list[str] = Field(default_factory=list)
|
||||||
|
already_present: list[str] = Field(default_factory=list)
|
||||||
|
total_tags: list[str] = Field(default_factory=list)
|
||||||
|
|||||||
@ -838,6 +838,66 @@ def get_asset_tags(session: Session, *, asset_info_id: str) -> list[str]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def add_tags_to_asset_info(
|
||||||
|
session: Session,
|
||||||
|
*,
|
||||||
|
asset_info_id: str,
|
||||||
|
tags: Sequence[str],
|
||||||
|
origin: str = "manual",
|
||||||
|
create_if_missing: bool = True,
|
||||||
|
asset_info_row: Any = None,
|
||||||
|
) -> dict:
|
||||||
|
if not asset_info_row:
|
||||||
|
info = session.get(AssetInfo, asset_info_id)
|
||||||
|
if not info:
|
||||||
|
raise ValueError(f"AssetInfo {asset_info_id} not found")
|
||||||
|
|
||||||
|
norm = normalize_tags(tags)
|
||||||
|
if not norm:
|
||||||
|
total = get_asset_tags(session, asset_info_id=asset_info_id)
|
||||||
|
return {"added": [], "already_present": [], "total_tags": total}
|
||||||
|
|
||||||
|
if create_if_missing:
|
||||||
|
ensure_tags_exist(session, norm, tag_type="user")
|
||||||
|
|
||||||
|
current = {
|
||||||
|
tag_name
|
||||||
|
for (tag_name,) in (
|
||||||
|
session.execute(
|
||||||
|
sa.select(AssetInfoTag.tag_name).where(AssetInfoTag.asset_info_id == asset_info_id)
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
}
|
||||||
|
|
||||||
|
want = set(norm)
|
||||||
|
to_add = sorted(want - current)
|
||||||
|
|
||||||
|
if to_add:
|
||||||
|
with session.begin_nested() as nested:
|
||||||
|
try:
|
||||||
|
session.add_all(
|
||||||
|
[
|
||||||
|
AssetInfoTag(
|
||||||
|
asset_info_id=asset_info_id,
|
||||||
|
tag_name=t,
|
||||||
|
origin=origin,
|
||||||
|
added_at=utcnow(),
|
||||||
|
)
|
||||||
|
for t in to_add
|
||||||
|
]
|
||||||
|
)
|
||||||
|
session.flush()
|
||||||
|
except IntegrityError:
|
||||||
|
nested.rollback()
|
||||||
|
|
||||||
|
after = set(get_asset_tags(session, asset_info_id=asset_info_id))
|
||||||
|
return {
|
||||||
|
"added": sorted(((after - current) & want)),
|
||||||
|
"already_present": sorted(want & current),
|
||||||
|
"total_tags": sorted(after),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def remove_missing_tag_for_asset_id(
|
def remove_missing_tag_for_asset_id(
|
||||||
session: Session,
|
session: Session,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ from app.assets.database.queries import (
|
|||||||
list_asset_infos_page,
|
list_asset_infos_page,
|
||||||
list_tags_with_usage,
|
list_tags_with_usage,
|
||||||
get_asset_tags,
|
get_asset_tags,
|
||||||
|
add_tags_to_asset_info,
|
||||||
pick_best_live_path,
|
pick_best_live_path,
|
||||||
ingest_fs_asset,
|
ingest_fs_asset,
|
||||||
set_asset_info_preview,
|
set_asset_info_preview,
|
||||||
@ -434,6 +435,31 @@ def create_asset_from_hash(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_tags_to_asset(
|
||||||
|
*,
|
||||||
|
asset_info_id: str,
|
||||||
|
tags: list[str],
|
||||||
|
origin: str = "manual",
|
||||||
|
owner_id: str = "",
|
||||||
|
) -> schemas_out.TagsAdd:
|
||||||
|
with create_session() as session:
|
||||||
|
info_row = get_asset_info_by_id(session, asset_info_id=asset_info_id)
|
||||||
|
if not info_row:
|
||||||
|
raise ValueError(f"AssetInfo {asset_info_id} not found")
|
||||||
|
if info_row.owner_id and info_row.owner_id != owner_id:
|
||||||
|
raise PermissionError("not owner")
|
||||||
|
data = add_tags_to_asset_info(
|
||||||
|
session,
|
||||||
|
asset_info_id=asset_info_id,
|
||||||
|
tags=tags,
|
||||||
|
origin=origin,
|
||||||
|
create_if_missing=True,
|
||||||
|
asset_info_row=info_row,
|
||||||
|
)
|
||||||
|
session.commit()
|
||||||
|
return schemas_out.TagsAdd(**data)
|
||||||
|
|
||||||
|
|
||||||
def list_tags(
|
def list_tags(
|
||||||
prefix: str | None = None,
|
prefix: str | None = None,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user