ComfyUI/app/assets/services
Simon Pinfold 34744cd29e
Add tags_all / tags_any / tags_none tag filters to the assets list API (#15332)
* Implement tags_all/tags_any/tags_none on the assets list API (BE-6600)

Adds the three canonically-named tag filter params to GET /api/assets and
GET /api/assets/tags/refine:

- tags_all: asset carries every tag (replaces include_tags)
- tags_any: asset carries at least one tag (new)
- tags_none: asset carries no tag (replaces exclude_tags)

Clauses intersect; tags_none always wins. include_tags/exclude_tags remain
as permanent deprecated aliases and behave exactly as before when used on
their own.

Invalid combinations return 400 INVALID_TAG_FILTER, but only when the
request uses at least one new-name parameter (non-empty after
normalisation):

- mixed spellings of one slot (include_tags with tags_all, exclude_tags
  with tags_none)
- the same tag in the effective all-list and none-list (query can never
  match)

Old-names-only requests gain no new error paths: include_tags=a&exclude_tags=a
still returns an empty 200. tags_any/tags_none overlap stays valid (dead
term, not a dead query).

* Address review findings: positional-compat, deprecation metadata, test matrix

- Move any_tags to the end of the four touched signatures: inserting it
  mid-signature silently misbound pre-existing positional callers (e.g.
  a caller passing name_contains positionally would have it consumed as
  any_tags).
- Mark include_tags/exclude_tags Field(deprecated=True) on both list
  schemas so generated schema metadata matches the contract, not just a
  comment (schemas_out.py already uses this form for Asset.name).
- Add tests: legal cross-slot old/new combinations, repeated query-key
  concatenation (pins Core behavior; outside the cross-platform
  contract), tags_any two-page cursor consistency (total/has_more/
  no-overlap), refine-route mixed-spelling rejection + legacy-conflict
  preservation, and schema deprecation metadata.

* Pin tag-value opacity: case-sensitive matching, byte-exact conflict check

The prod tag survey (~/comfy/prod-model-tag-shape.md) found live
case-distinct tag pairs (SEEDVR2/seedvr2) that resolve differently, so
the contract now states tag values are opaque byte-strings. Pin that:
case-distinct tags filter separately, and a case-distinct all/none pair
is not an INVALID_TAG_FILTER conflict.

* Document tags_all/tags_any/tags_none in openapi.yaml, deprecate aliases

Add the three tag-filter parameters to both listAssets and
getAssetTagHistogram parameter blocks and mark include_tags/exclude_tags
deprecated: true, keeping the spec in step with the runtime schemas so
generated clients can discover the new filters while the aliases stay
present for existing consumers.

* Move schemas_in import to module scope in test_list_filter

Review feedback: no import cycle requires the local import.

* Silence per-request DeprecationWarning in the tag-filter remap shim

Reading the deprecated include_tags/exclude_tags fields by attribute
fires pydantic's DeprecationWarning on every list/refine request even
for callers using only the new names. The warning is aimed at API
clients, not the server's own remap; read via model_dump instead.

* Cap tag-filter lists at 100 entries, all spellings

Review finding: unbounded tag lists fan out into one correlated EXISTS
per tag on both page and count statements. Cap each list at 100
normalized entries with 400 INVALID_TAG_FILTER naming the parameter.

Applies to the legacy spellings as well — a deliberate, decided
exception to the old-names-behave-identically rule, since a cap only on
new names would leave the same fan-out reachable through the aliases.

* Strip process narration from comments

Comments carried decision dates, contract cross-references, and review
context. Keep only the constraints the code cannot show, one line each.
2026-08-10 14:05:21 -07:00
..
__init__.py feat(assets): register output files as assets after prompt execution (#12812) 2026-03-24 20:48:55 -07:00
asset_management.py Add tags_all / tags_any / tags_none tag filters to the assets list API (#15332) 2026-08-10 14:05:21 -07:00
bulk_ingest.py feat(assets): add namespaced model_type tags and align tag semantics (#14511) 2026-07-08 22:00:08 -07:00
cursor.py feat(assets): cursor-based pagination on GET /api/assets (#14014) 2026-06-09 21:14:03 -07:00
file_utils.py refactor(assets): modular architecture + async two-phase scanner & background seeder (#12621) 2026-03-07 20:37:25 -05:00
hashing.py Import assets seeder later, print some package versions. (#12841) 2026-03-08 16:25:30 -04:00
image_dimensions.py feat(assets): extract image dimensions at ingest and emit on asset responses (#13991) 2026-06-08 14:27:50 -07:00
ingest.py feat(assets): add namespaced model_type tags and align tag semantics (#14511) 2026-07-08 22:00:08 -07:00
metadata_extract.py Remove useless annotations imports. (#14105) 2026-05-25 19:23:29 -07:00
path_utils.py feat(assets): add namespaced model_type tags and align tag semantics (#14511) 2026-07-08 22:00:08 -07:00
schemas.py feat(assets): add namespaced model_type tags and align tag semantics (#14511) 2026-07-08 22:00:08 -07:00
tagging.py Add tags_all / tags_any / tags_none tag filters to the assets list API (#15332) 2026-08-10 14:05:21 -07:00