Skip to content

Add tags_all / tags_any / tags_none tag filters to the assets list API - #15332

Merged
synap5e merged 9 commits into
masterfrom
synap5e/be-6600-core-implement-tags_all-tags_any-tags_none-on-the-assets
Aug 10, 2026
Merged

Add tags_all / tags_any / tags_none tag filters to the assets list API#15332
synap5e merged 9 commits into
masterfrom
synap5e/be-6600-core-implement-tags_all-tags_any-tags_none-on-the-assets

Conversation

@synap5e

@synap5e synap5e commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

The assets list API can express "has all of these tags" and "has none of these tags", but not "has any of these tags". This adds that operation, and gives the three a consistent naming scheme.

Parameter Matches when the asset carries Replaces
tags_all every tag in the list include_tags
tags_any at least one tag in the list — (new)
tags_none no tag in the list exclude_tags

Clauses intersect, tags_none always wins, and an empty list is equivalent to an absent parameter.

The motivating case: selecting all generated assets means outputtemp, which could not be expressed with an all-of filter.

Backwards compatibility

include_tags and exclude_tags keep working permanently, as deprecated aliases (Field(deprecated=True)).

Combination validation applies only when a request uses at least one new-name parameter. Requests using only the old names take an early return before any validation runs, so their behaviour is unchanged by construction rather than by careful condition-writing — including the degenerate include_tags=a&exclude_tags=a, which still returns an empty 200.

Error behaviour

When a new-name parameter is present, invalid combinations return 400 INVALID_TAG_FILTER, with details naming the parameters as the caller spelled them:

  • both spellings of one slot — include_tags with tags_all, or exclude_tags with tags_none
  • the same tag in the effective all-list and none-list, which can never match

Deliberately not rejected: tags_any/tags_none overlap (a dead term, not a dead query — legitimate when composing lists from independent sources), tags_all/tags_any redundancy, in-list duplicates, and unknown tag values. The tag vocabulary is open-ended, so an unknown tag is not an error — it simply matches nothing.

Tag value semantics

Tag values are compared as opaque byte strings, exactly and case-sensitively. No case-folding, no trimming inside values, no normalisation — case-distinct tags are distinct tags. List-level whitespace handling and de-duplication apply to the parameter syntax, not to the values themselves.

Implementation notes

The any-of clause is added to apply_tag_filters and applied in both consumers — GET /api/assets and GET /api/assets/tags/refineincluding their count queries, so total and has_more stay consistent with page contents.

Repeated occurrences of a single query key (tags_any=a&tags_any=b) are concatenated before the CSV split. This is local behaviour rather than a cross-implementation guarantee, and is pinned by a test.

Testing

Full assets suite: 489 passed, 10 skipped (skips pre-existing). ruff check clean.

New coverage: tags_any alone and combined with tags_all; tags_none taking precedence over a tags_any match; empty values behaving as absent, including alongside old names; old-name/new-name result equivalence; cross-slot old+new combinations; both rejection classes with details payloads asserted; dead-term acceptance; a legacy-only conflict still returning 200; case-sensitive matching and byte-exact conflict checking; repeated-key concatenation; two-page tags_any keyset pagination (total, has_more, no overlap); and schema deprecation metadata on both models.

@synap5e synap5e added the cursor-review Trigger multi-model Cursor code review label Aug 5, 2026
synap5e added 3 commits August 5, 2026 13:14
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).
…t 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.
…heck

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.
@synap5e
synap5e force-pushed the synap5e/be-6600-core-implement-tags_all-tags_any-tags_none-on-the-assets branch from 52c0155 to e204ab9 Compare August 5, 2026 20:14
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6762192d-4c44-4f09-b3e5-40eda83a9043

📥 Commits

Reviewing files that changed from the base of the PR and between 43ccc2b and b91eefb.

📒 Files selected for processing (2)
  • app/assets/api/routes.py
  • tests-unit/assets_test/test_list_filter.py
📝 Walkthrough

Walkthrough

The API adds tags_all, tags_any, and tags_none filters while retaining deprecated legacy aliases. Shared resolution normalizes inputs and returns structured INVALID_TAG_FILTER errors for invalid combinations. Asset and tag queries apply any-tag filtering through EXISTS predicates. Tests cover filtering, validation, aliases, pagination, refinement, case sensitivity, repeated parameters, and schema metadata.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the addition of the three new tag filters to the assets list API.
Description check ✅ Passed The description accurately covers the new filters, aliases, validation, semantics, pagination, documentation, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52c0155e88

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/assets/api/schemas_in.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests-unit/assets_test/test_list_filter.py`:
- Line 719: Move the schemas_in import from its local location into module scope
in tests-unit/assets_test/test_list_filter.py, keeping the existing
app.assets.api import and usage unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ce6f70cf-dd96-4615-8437-0d3e563bed59

📥 Commits

Reviewing files that changed from the base of the PR and between 6f7cd7f and e204ab9.

📒 Files selected for processing (8)
  • app/assets/api/routes.py
  • app/assets/api/schemas_in.py
  • app/assets/database/queries/asset_reference.py
  • app/assets/database/queries/common.py
  • app/assets/database/queries/tags.py
  • app/assets/services/asset_management.py
  • app/assets/services/tagging.py
  • tests-unit/assets_test/test_list_filter.py
📜 Review details
⏰ Context from checks skipped due to timeout. (8)
  • GitHub Check: test (windows-2022)
  • GitHub Check: test (ubuntu-latest)
  • GitHub Check: test (macos-latest)
  • GitHub Check: test (windows-latest)
  • GitHub Check: Run Pylint
  • GitHub Check: test (macos-latest)
  • GitHub Check: test (ubuntu-latest)
  • GitHub Check: Run Pylint
🧰 Additional context used
📓 Path-based instructions (5)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Keep changes small, direct, and limited to the narrowest necessary code path and smallest number of files.
Prefer practical fixes, minimal dependencies, and existing repository patterns; remove obsolete, dead, unreachable, or unused code.
Preserve existing APIs, node names, model-loading behavior, file layout, and workflow compatibility unless replacement is explicitly intended.
Core ComfyUI must not add outbound internet requests, telemetry, tracking, reporting, remote configuration, or background network activity. User-authorized model downloads are limited to the requested artifact and must exclude telemetry and unrelated metadata.

Files:

  • app/assets/database/queries/tags.py
  • app/assets/database/queries/common.py
  • app/assets/services/tagging.py
  • app/assets/services/asset_management.py
  • app/assets/api/routes.py
  • app/assets/api/schemas_in.py
  • app/assets/database/queries/asset_reference.py
  • tests-unit/assets_test/test_list_filter.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Keep state and capability flags on the object that owns the behavior. Prefer explicit parent-owned attributes over probing child objects with getattr; use child checks only when the child owns the delegated behavior.
Preserve shared method signatures, argument order, return shapes, side effects, and error behavior unless every affected caller and interface is intentionally updated.
Do not add unused compatibility parameters, flags, attributes, constructor options, or model-specific options to shared helpers; keep one-off behavior at the integration boundary.
Normalize third-party return conventions at integration boundaries so core code receives the expected type and shape; avoid undocumented caller-side unwrapping.
Do not add torch.no_grad, torch.inference_mode, or inference-mode wrappers. Do not add model freeze/unfreeze toggles; only disable globally enabled inference mode when a training path requires gradients.
Remove inference-only training behavior such as dropout while preserving checkpoint and state-dict compatibility; use nn.Identity when deleting a module would alter keys or ordering.
Keep imports at module scope except established optional-backend probes or imports required to avoid cycles; avoid unnecessary try/except blocks and use specific exceptions with useful fallbacks.
Do not add workarounds for unsupported library versions, especially PyTorch exception-and-float-cast retries, unless a comment names the exact versions still requiring them.
Let unsupported model formats, invalid quantization metadata, and bad states fail with clear errors instead of silently degrading output.
Match local style, keep comments sparse and useful, and remove comments that merely restate obvious code.
Treat dtype, device placement, VRAM use, and offloading as correctness concerns across CPU, CUDA, ROCm, MPS, DirectML, XPU, NPU, and low-VRAM environments.
Prefer existing ComfyUI and Comfy Kitchen operations, quantization helpers, cast/offload helpe...

Files:

  • app/assets/database/queries/tags.py
  • app/assets/database/queries/common.py
  • app/assets/services/tagging.py
  • app/assets/services/asset_management.py
  • app/assets/api/routes.py
  • app/assets/api/schemas_in.py
  • app/assets/database/queries/asset_reference.py
  • tests-unit/assets_test/test_list_filter.py
**/*.{py,json}

📄 CodeRabbit inference engine (AGENTS.md)

Treat legacy combo, io.Combo, and io.DynamicCombo values affecting filesystem access as untrusted; revalidate them at load/save boundaries with folder_paths, containment checks, or fixed allowlists.

Files:

  • app/assets/database/queries/tags.py
  • app/assets/database/queries/common.py
  • app/assets/services/tagging.py
  • app/assets/services/asset_management.py
  • app/assets/api/routes.py
  • app/assets/api/schemas_in.py
  • app/assets/database/queries/asset_reference.py
  • tests-unit/assets_test/test_list_filter.py
**/*.{py,md,txt,json}

📄 CodeRabbit inference engine (AGENTS.md)

Keep warning and info messages short and actionable, remove noisy or misleading logging, and make documentation edits concise, factual, and tied to changed behavior.

Files:

  • app/assets/database/queries/tags.py
  • app/assets/database/queries/common.py
  • app/assets/services/tagging.py
  • app/assets/services/asset_management.py
  • app/assets/api/routes.py
  • app/assets/api/schemas_in.py
  • app/assets/database/queries/asset_reference.py
  • tests-unit/assets_test/test_list_filter.py
**

⚙️ CodeRabbit configuration file

**: IMPORTANT: Only comment on issues directly introduced by this PR's code changes.
Treat AGENTS.md as mandatory repository policy, not optional style guidance.
Flag PR changes that violate AGENTS.md even when the code is otherwise functional.
In particular, enforce architecture boundaries, dtype/device/memory rules,
interface contracts, import style, no unnecessary try/except blocks, no inline
imports, no outbound internet paths in core ComfyUI, and narrow scoped fixes.
Prefer direct findings over suggestions when a rule is violated. Only ignore
AGENTS.md when it clearly conflicts with a newer explicit maintainer instruction
in the PR.
Do NOT flag pre-existing issues in code that was merely moved, re-indented,
de-indented, or reformatted without logic changes. If code appears in the diff
only due to whitespace or structural reformatting (e.g., removing a with: block),
treat it as unchanged. Contributors should not feel obligated to address
pre-existing issues outside the scope of their contribution.

Files:

  • app/assets/database/queries/tags.py
  • app/assets/database/queries/common.py
  • app/assets/services/tagging.py
  • app/assets/services/asset_management.py
  • app/assets/api/routes.py
  • app/assets/api/schemas_in.py
  • app/assets/database/queries/asset_reference.py
  • tests-unit/assets_test/test_list_filter.py
🧠 Learnings (1)
📚 Learning: 2026-02-21T14:01:41.482Z
Learnt from: pythongosssss
Repo: Comfy-Org/ComfyUI PR: 12555
File: comfy_extras/nodes_glsl.py:719-724
Timestamp: 2026-02-21T14:01:41.482Z
Learning: In PyOpenGL, bare Python scalars can be accepted for 1-element array parameters by NumberHandler. This means you can pass an int/float directly to OpenGL texture deletion (e.g., glDeleteTextures(tex)) without wrapping in a list. Verify function-specific expectations and ensure types match what the OpenGL call expects; use explicit lists only when the API requires an array.

Applied to files:

  • app/assets/database/queries/tags.py
  • app/assets/database/queries/common.py
  • app/assets/services/tagging.py
  • app/assets/services/asset_management.py
  • app/assets/api/routes.py
  • app/assets/api/schemas_in.py
  • app/assets/database/queries/asset_reference.py
  • tests-unit/assets_test/test_list_filter.py
🔇 Additional comments (8)
app/assets/api/routes.py (1)

21-21: LGTM!

Also applies to: 120-176, 277-291, 781-790

app/assets/api/schemas_in.py (1)

53-58: LGTM!

Also applies to: 77-80, 164-177

app/assets/services/asset_management.py (1)

282-283: LGTM!

Also applies to: 322-322

app/assets/services/tagging.py (1)

88-97: LGTM!

app/assets/database/queries/tags.py (1)

343-344: LGTM!

Also applies to: 364-364

app/assets/database/queries/asset_reference.py (1)

271-272: LGTM!

Also applies to: 298-298, 350-350

app/assets/database/queries/common.py (1)

63-69: LGTM!

Also applies to: 80-87

tests-unit/assets_test/test_list_filter.py (1)

340-716: LGTM!

Comment thread tests-unit/assets_test/test_list_filter.py Outdated
synap5e added 2 commits August 5, 2026 13:20
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.
Review feedback: no import cycle requires the local import.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 5, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Cursor Review — Consolidated panel

Triggered by @synap5e.

Found 4 finding(s).

Severity Count
🟢 Low 3
⚪ Nit 1

Panel: 8/8 reviewers contributed findings.

Comment thread app/assets/api/schemas_in.py
Comment thread app/assets/api/routes.py
("tags_none", tags_none) if tags_none else ("exclude_tags", exclude_tags)
)

conflicting = sorted(set(all_list) & set(none_list))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low — The all/none conflict check raises INVALID_TAG_FILTER, but tags_any is never checked against none_list. When every tags_any candidate is also in tags_none (e.g. tags_any=a&tags_none=a) the query is unsatisfiable yet returns an empty 200 instead of a 400, unlike the analogous all/none case; partial overlap should stay valid (as test_list_assets_any_none_overlap_accepted asserts). Raised by 1 of 8 reviewers (gpt-5.6-sol-max edge-case).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intended and documented — rejection is scoped to the all/none contradiction; any/none overlap stays valid even at full coverage since callers compose those lists from independent sources.

Comment thread app/assets/api/routes.py
("tags_all", tags_all) if tags_all else ("include_tags", include_tags)
)
none_param, none_list = (
("tags_none", tags_none) if tags_none else ("exclude_tags", exclude_tags)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low — Once any new-name parameter is present, the conflict check runs on the remapped legacy include_tags/exclude_tags, so a self-contradictory legacy pair like include_tags=a&exclude_tags=a returns the historical empty 200 on its own but flips to a 400 when an unrelated tags_any is added. Confirm this behavior change for legacy-only clients is intended. Raised by 1 of 8 reviewers (kimi-k2.7-code edge-case).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intended — a new-name parameter opts the whole request into validation (run post-remap); legacy-only requests are never newly rejected.

Comment thread app/assets/api/routes.py Outdated
the legacy names keep their historical behaviour, including degenerate
combinations like include_tags=a&exclude_tags=a.
"""
include_tags = normalize_tags(q.include_tags)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit_resolve_tag_filters reads q.include_tags and q.exclude_tags unconditionally, but both are Field(deprecated=True), so Pydantic (>=2.7) emits a DeprecationWarning on every /api/assets and /api/assets/tags/refine request even when the client used only the new tags_all/tags_any/tags_none params. Read the legacy fields only when they are the effective source so the deprecation signal tracks actual legacy use. Raised by 1 of 8 reviewers (claude-opus-4-8-thinking-max edge-case).

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.
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.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 5, 2026
Comments carried decision dates, contract cross-references, and review
context. Keep only the constraints the code cannot show, one line each.
@synap5e
synap5e merged commit 34744cd into master Aug 10, 2026
23 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

cursor-review Trigger multi-model Cursor code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants