From 7e16be430dcb64ad05e85b4ef814af69318a14c9 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 25 Oct 2026 16:16:35 -0600 Subject: [PATCH] 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 --- .../2026_10_25_1200_strip_tag_names.py | 22 ++++++++++++------- wrolpi/test/test_db_bootstrap.py | 7 ++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/alembic/versions/2026_10_25_1200_strip_tag_names.py b/alembic/versions/2026_10_25_1200_strip_tag_names.py index 60a83fe8..a076bbf8 100644 --- a/alembic/versions/2026_10_25_1200_strip_tag_names.py +++ b/alembic/versions/2026_10_25_1200_strip_tag_names.py @@ -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(): diff --git a/wrolpi/test/test_db_bootstrap.py b/wrolpi/test/test_db_bootstrap.py index 9b520755..4ac0def2 100644 --- a/wrolpi/test/test_db_bootstrap.py +++ b/wrolpi/test/test_db_bootstrap.py @@ -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())