tags.yaml keeps the database's color for merged Tags

The file was merged in sorted key order, not by Tag id, and the next
import copies the file's color over the database's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-10-25 16:16:35 -06:00
parent 732e497323
commit 7e16be430d
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 19 additions and 10 deletions

View file

@ -77,7 +77,9 @@ def _strip_rows(rows: list, key_length: int) -> list:
return result
def _strip_tags_config(config: dict) -> dict:
def _strip_tags_config(config: dict, colors: dict) -> dict:
"""`colors`: each renamed Tag's color in the database, which the file must match (an import copies the file's
color into the database)."""
config = dict(config)
if isinstance(config.get('tags'), dict):
tags = dict()
@ -88,6 +90,8 @@ def _strip_tags_config(config: dict) -> dict:
elif isinstance(attrs, dict) and attrs.get('color') and not (tags[stripped] or dict()).get('color'):
# The first color of the merged Tags.
tags[stripped] = {**(tags[stripped] or dict()), 'color': attrs['color']}
for name in tags.keys() & colors.keys():
tags[name] = {**(tags[name] or dict()), 'color': colors[name]}
config['tags'] = tags
if isinstance(config.get('tag_files'), list):
config['tag_files'] = _strip_rows(config['tag_files'], 2)
@ -96,7 +100,7 @@ def _strip_tags_config(config: dict) -> dict:
return config
def strip_tag_names_in_configs():
def strip_tag_names_in_configs(colors: dict):
config_directory = get_media_directory() / 'config'
for file_name in CONFIG_FILES:
file = config_directory / file_name
@ -110,15 +114,16 @@ def strip_tag_names_in_configs():
continue
if not isinstance(config, dict):
continue
stripped = _strip_tags_config(config) if file_name == 'tags.yaml' else config
stripped = _strip_tags_config(config, colors) if file_name == 'tags.yaml' else config
stripped = _strip_nested(stripped)
if stripped != config:
logger.warning(f'Stripping whitespace from Tag names in {file}')
write_config_data(stripped, file, width=500 if file_name == 'tags.yaml' else 90)
def strip_tag_names_in_db(connection):
groups = dict()
def strip_tag_names_in_db(connection) -> dict:
"""Returns the color of each renamed Tag, by its new name."""
groups, colors = dict(), dict()
for id_, name, color in connection.exec_driver_sql('SELECT id, name, color FROM tag ORDER BY id').fetchall():
groups.setdefault(_strip(name), list()).append((id_, name, color))
@ -127,7 +132,7 @@ def strip_tag_names_in_db(connection):
continue
# Keep the Tag already named this, if any, otherwise the oldest.
survivor_id = next((id_ for id_, name, _ in tags if name == stripped), tags[0][0])
color = next((color for _, _, color in tags if color), None)
color = colors[stripped] = next((color for _, _, color in tags if color), None)
for id_, name, _ in tags:
if id_ == survivor_id:
continue
@ -157,11 +162,12 @@ def strip_tag_names_in_db(connection):
'UPDATE download SET tag_names = ?, settings = ? WHERE id = ?',
(json.dumps(new_tag_names) if new_tag_names is not None else None,
json.dumps(new_settings) if new_settings is not None else None, id_))
return colors
def upgrade():
strip_tag_names_in_db(op.get_bind())
strip_tag_names_in_configs()
colors = strip_tag_names_in_db(op.get_bind())
strip_tag_names_in_configs(colors)
def downgrade():

View file

@ -342,7 +342,9 @@ def test_strip_tag_names_migration_merges_tags(test_directory):
conn.commit()
(config_directory / 'tags.yaml').write_text(yaml.dump(dict(
tags={' one': dict(color=None), 'one ': dict(color='#222222'), 'unchanged': dict(color='#777777')},
# Sorted, ' two ' comes before 'two\t'; the database's color (by id) is kept anyway.
tags={' one': dict(color=None), 'one ': dict(color='#222222'), ' two ': dict(color='#555555'),
'two\t': dict(color='#444444'), 'unchanged': dict(color='#777777')},
tag_files=[[' one', '10.mp4', None], ['one ', '10.mp4', None], ['unchanged', '10.mp4', None]],
tag_zims=[],
version=3,
@ -366,7 +368,8 @@ def test_strip_tag_names_migration_merges_tags(test_directory):
assert json.loads(settings) == {'channel_tag_name': ['two'], 'video_resolutions': ['720p']}
tags_config = yaml.safe_load((config_directory / 'tags.yaml').read_text())
assert tags_config['tags'] == {'one': dict(color='#222222'), 'unchanged': dict(color='#777777')}
assert tags_config['tags'] == {'one': dict(color='#222222'), 'two': dict(color='#444444'),
'unchanged': dict(color='#777777')}
assert tags_config['tag_files'] == [['one', '10.mp4', None], ['unchanged', '10.mp4', None]]
assert tags_config['version'] == 3
channels_config = yaml.safe_load((config_directory / 'channels.yaml').read_text())