mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-31 02:02:22 +01:00
fix(gallery): match stacked CLI filters against AI tags
This commit is contained in:
@@ -6,6 +6,7 @@ that are awkward at the shell). Filters images by tag, prompt, favorite,
|
|||||||
album, and surfaces EXIF metadata.
|
album, and surfaces EXIF metadata.
|
||||||
|
|
||||||
odysseus-gallery list --limit 20 --favorites
|
odysseus-gallery list --limit 20 --favorites
|
||||||
|
odysseus-gallery list --tag "sunset,beach" # all tags, manual or AI
|
||||||
odysseus-gallery show IMAGE_ID
|
odysseus-gallery show IMAGE_ID
|
||||||
odysseus-gallery albums | jq -r '.[] | .name'
|
odysseus-gallery albums | jq -r '.[] | .name'
|
||||||
odysseus-gallery search "sunset" # matches prompt + tags
|
odysseus-gallery search "sunset" # matches prompt + tags
|
||||||
@@ -83,7 +84,13 @@ def cmd_list(args):
|
|||||||
fail(f"no album named {args.album!r}")
|
fail(f"no album named {args.album!r}")
|
||||||
q = q.filter(GalleryImage.album_id == al.id)
|
q = q.filter(GalleryImage.album_id == al.id)
|
||||||
if args.tag:
|
if args.tag:
|
||||||
q = q.filter(GalleryImage.tags.ilike(f"%{args.tag}%"))
|
for tag in (value.strip() for value in args.tag.split(",")):
|
||||||
|
if not tag:
|
||||||
|
continue
|
||||||
|
q = q.filter(
|
||||||
|
GalleryImage.tags.ilike(f"%{tag}%") |
|
||||||
|
GalleryImage.ai_tags.ilike(f"%{tag}%")
|
||||||
|
)
|
||||||
q = q.order_by(GalleryImage.created_at.desc()).limit(args.limit)
|
q = q.order_by(GalleryImage.created_at.desc()).limit(args.limit)
|
||||||
emit([_serialize_image(i) for i in q.all()], args)
|
emit([_serialize_image(i) for i in q.all()], args)
|
||||||
finally:
|
finally:
|
||||||
@@ -159,7 +166,7 @@ def _build_parser():
|
|||||||
pl.add_argument("--limit", type=int, default=50)
|
pl.add_argument("--limit", type=int, default=50)
|
||||||
pl.add_argument("--favorites", action="store_true")
|
pl.add_argument("--favorites", action="store_true")
|
||||||
pl.add_argument("--album", help="filter by album name")
|
pl.add_argument("--album", help="filter by album name")
|
||||||
pl.add_argument("--tag", help="substring match against tags column")
|
pl.add_argument("--tag", help="comma-separated tags; each matches manual or AI tags by substring")
|
||||||
pl.set_defaults(func=cmd_list)
|
pl.set_defaults(func=cmd_list)
|
||||||
|
|
||||||
psh = sub.add_parser("show", parents=[common])
|
psh = sub.add_parser("show", parents=[common])
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
"""Gallery CLI tag filters match both sources and stack like the web library."""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from core.database import Base, GalleryAlbum, GalleryImage
|
||||||
|
from tests.helpers.cli_loader import load_script
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def gallery_cli(monkeypatch, tmp_path):
|
||||||
|
engine = create_engine(f"sqlite:///{tmp_path / 'gallery.db'}")
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
sessions = sessionmaker(bind=engine)
|
||||||
|
with sessions() as db:
|
||||||
|
db.add(GalleryAlbum(id='sea', name='Sea'))
|
||||||
|
for index, (uid, tags, ai_tags, favorite, album) in enumerate([
|
||||||
|
('manual', 'sunset,beach', '', True, 'sea'),
|
||||||
|
('ai', '', 'Beach,sunset', False, 'sea'),
|
||||||
|
('split', 'sunset', 'beach', True, None),
|
||||||
|
('one', 'sunset', '', False, None),
|
||||||
|
('other', 'forest', 'moss', False, None),
|
||||||
|
('inactive', 'sunset,beach', 'sunset,beach', True, 'sea'),
|
||||||
|
]):
|
||||||
|
db.add(GalleryImage(id=uid, filename=uid+'.png', tags=tags, ai_tags=ai_tags,
|
||||||
|
favorite=favorite, album_id=album, is_active=uid!='inactive',
|
||||||
|
created_at=datetime(2026, 1, 1) + timedelta(minutes=index)))
|
||||||
|
db.commit()
|
||||||
|
cli = load_script('odysseus-gallery')
|
||||||
|
monkeypatch.setattr(cli, 'SessionLocal', sessions)
|
||||||
|
output = []
|
||||||
|
monkeypatch.setattr(cli, 'emit', lambda rows, args: output.extend(rows))
|
||||||
|
|
||||||
|
def run(*argv):
|
||||||
|
output.clear()
|
||||||
|
args = cli._build_parser().parse_args(['list', *argv])
|
||||||
|
args.func(args)
|
||||||
|
return [row['id'] for row in output]
|
||||||
|
|
||||||
|
yield run
|
||||||
|
engine.dispose()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('tag,expected', [
|
||||||
|
('sunset', ['one', 'split', 'ai', 'manual']),
|
||||||
|
('beach,sunset', ['split', 'ai', 'manual']),
|
||||||
|
('sunset,beach', ['split', 'ai', 'manual']),
|
||||||
|
(', sunset, , BEACH ,', ['split', 'ai', 'manual']),
|
||||||
|
('forest', ['other']),
|
||||||
|
('no-match', []),
|
||||||
|
(' , ', ['other', 'one', 'split', 'ai', 'manual']),
|
||||||
|
('sun', ['one', 'split', 'ai', 'manual']),
|
||||||
|
])
|
||||||
|
def test_tag_sources_and_stacking(gallery_cli, tag, expected):
|
||||||
|
assert gallery_cli('--tag', tag) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('options,expected', [
|
||||||
|
([], ['other', 'one', 'split', 'ai', 'manual']),
|
||||||
|
(['--tag', 'beach,sunset', '--favorites'], ['split', 'manual']),
|
||||||
|
(['--tag', 'beach,sunset', '--album', 'Sea'], ['ai', 'manual']),
|
||||||
|
(['--tag', 'beach', '--limit', '1'], ['split']),
|
||||||
|
])
|
||||||
|
def test_tag_filters_preserve_other_list_options(gallery_cli, options, expected):
|
||||||
|
assert gallery_cli(*options) == expected
|
||||||
Reference in New Issue
Block a user