From b0b94e059fba69c0b56786c37660731e1f153e2f Mon Sep 17 00:00:00 2001 From: Squeaky Date: Wed, 13 Nov 2024 14:24:19 +0100 Subject: [PATCH 01/11] First read-only draft --- bin/recategorize-messages.py | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 bin/recategorize-messages.py diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py new file mode 100755 index 000000000..911c13062 --- /dev/null +++ b/bin/recategorize-messages.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +import datetime + +import click +from sqlalchemy.orm import Query + +from inbox.mailsync.backends.imap.common import update_message_metadata +from inbox.models.backends.imap import ImapUid +from inbox.models.folder import Folder +from inbox.models.message import Message +from inbox.models.namespace import Namespace +from inbox.models.session import global_session_scope, session_scope + + +def fetch_message_ids( + *, + account_id: int | None, + date_start: datetime.date | None, + date_end: datetime.date | None, + only_inbox: bool, +) -> list[int]: + query = Query([Message.id]) + if account_id: + query = query.filter(Message.namespace.has(Namespace.account_id == account_id)) + if only_inbox: + inbox_folder = ImapUid.folder.has(Folder._canonical_name == "INBOX") + query = query.filter(Message.imapuids.any(inbox_folder)) + if date_start: + query = query.filter(Message.created_at >= date_start) + if date_end: + query = query.filter(Message.created_at < date_end) + + with global_session_scope() as session: + message_ids = [message_id for message_id, in query.with_session(session)] + + return message_ids + + +@click.command() +@click.option("--date-start", type=click.DateTime(formats=["%Y-%m-%d"]), default=None) +@click.option("--date-end", type=click.DateTime(formats=["%Y-%m-%d"]), default=None) +@click.option("--account-id", type=int, default=None) +@click.option("--only-inbox", is_flag=True, default=False) +def main( + account_id: int | None, + only_inbox: bool, + date_start: datetime.date | None, + date_end: datetime.date | None, +) -> None: + message_ids = fetch_message_ids( + account_id=account_id, + date_start=date_start, + date_end=date_end, + only_inbox=only_inbox, + ) + + print(f"Found {len(message_ids)}") + + for message_id in message_ids: + with session_scope(None) as session: + message = session.query(Message).get(message_id) + old_categories = set( + category.display_name for category in message.categories + ) + update_message_metadata(session, message.account, message, message.is_draft) + new_categories = set( + category.display_name for category in message.categories + ) + if old_categories != new_categories: + print( + f"Message {message_id} categories changed from {old_categories} to {new_categories}" + ) + + +if __name__ == "__main__": + main() From 4b516b1e3b95ecda0cf7b1a67309ad3581568a47 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Wed, 13 Nov 2024 17:10:16 +0100 Subject: [PATCH 02/11] Evolution --- bin/recategorize-messages.py | 95 +++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index 911c13062..e71011898 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import datetime +from collections.abc import Iterable import click from sqlalchemy.orm import Query @@ -13,64 +14,88 @@ from inbox.models.session import global_session_scope, session_scope -def fetch_message_ids( +def yield_account_id_and_message_ids( *, - account_id: int | None, + only_account_id: int | None, date_start: datetime.date | None, date_end: datetime.date | None, only_inbox: bool, -) -> list[int]: - query = Query([Message.id]) - if account_id: - query = query.filter(Message.namespace.has(Namespace.account_id == account_id)) - if only_inbox: - inbox_folder = ImapUid.folder.has(Folder._canonical_name == "INBOX") - query = query.filter(Message.imapuids.any(inbox_folder)) - if date_start: - query = query.filter(Message.created_at >= date_start) - if date_end: - query = query.filter(Message.created_at < date_end) +) -> Iterable[int, list[int]]: + namespace_query = Query([Namespace.account_id, Namespace.id]) + if only_account_id: + namespace_query = namespace_query.filter( + Namespace.account_id == only_account_id + ) with global_session_scope() as session: - message_ids = [message_id for message_id, in query.with_session(session)] + account_id_to_namespace_id = { + account_id: namespace_id + for account_id, namespace_id in namespace_query.with_session(session) + } - return message_ids + for account_id, namespace_id in account_id_to_namespace_id.items(): + query = Query([Message.id]).filter(Message.namespace_id == namespace_id) + + if only_inbox: + inbox_folder = ImapUid.folder.has(Folder._canonical_name == "INBOX") + query = query.filter(Message.imapuids.any(inbox_folder)) + if date_start: + query = query.filter(Message.created_at >= date_start) + if date_end: + query = query.filter(Message.created_at < date_end) + + with global_session_scope() as session: + message_ids = [message_id for message_id, in query.with_session(session)] + + yield account_id, message_ids @click.command() @click.option("--date-start", type=click.DateTime(formats=["%Y-%m-%d"]), default=None) @click.option("--date-end", type=click.DateTime(formats=["%Y-%m-%d"]), default=None) -@click.option("--account-id", type=int, default=None) +@click.option("--only-account-id", type=int, default=None) @click.option("--only-inbox", is_flag=True, default=False) +@click.option("--dry-run/--no-dry-run", default=True) def main( - account_id: int | None, + only_account_id: int | None, only_inbox: bool, date_start: datetime.date | None, date_end: datetime.date | None, + dry_run: bool, ) -> None: - message_ids = fetch_message_ids( - account_id=account_id, + print( + f"Settings: {only_account_id=}, {only_inbox=}, {date_start=}, {date_end=}, {dry_run=}\n" + ) + + def session_factory(): + return global_session_scope() if dry_run else session_scope(None) + + for account_id, message_ids in yield_account_id_and_message_ids( + only_account_id=only_account_id, date_start=date_start, date_end=date_end, only_inbox=only_inbox, - ) + ): + print(f"{account_id=}, {len(message_ids)=}") - print(f"Found {len(message_ids)}") - - for message_id in message_ids: - with session_scope(None) as session: - message = session.query(Message).get(message_id) - old_categories = set( - category.display_name for category in message.categories - ) - update_message_metadata(session, message.account, message, message.is_draft) - new_categories = set( - category.display_name for category in message.categories - ) - if old_categories != new_categories: - print( - f"Message {message_id} categories changed from {old_categories} to {new_categories}" + changed_counter = 0 + for message_id in message_ids: + with session_factory() as session: + message = session.query(Message).get(message_id) + old_categories = set( + category.display_name for category in message.categories + ) + update_message_metadata( + session, message.account, message, message.is_draft ) + new_categories = set( + category.display_name for category in message.categories + ) + if old_categories != new_categories: + changed_counter += 1 + print(f"\t{message_id=}, {old_categories=} to {new_categories}=") + + print(f"{account_id=}, {changed_counter=}\n") if __name__ == "__main__": From f60a314273dce10031e3aa0d6812e9d828f7d9fb Mon Sep 17 00:00:00 2001 From: Squeaky Date: Wed, 13 Nov 2024 18:14:56 +0100 Subject: [PATCH 03/11] Progress --- bin/recategorize-messages.py | 42 +++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index e71011898..fc7d4e4f8 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -14,6 +14,17 @@ from inbox.models.session import global_session_scope, session_scope +def get_total_namespace_count(*, only_account_id: int | None) -> int: + namespace_query = Query([Namespace.id]) + if only_account_id: + namespace_query = namespace_query.filter( + Namespace.account_id == only_account_id + ) + + with global_session_scope() as session: + return namespace_query.with_session(session).count() + + def yield_account_id_and_message_ids( *, only_account_id: int | None, @@ -67,14 +78,20 @@ def main( f"Settings: {only_account_id=}, {only_inbox=}, {date_start=}, {date_end=}, {dry_run=}\n" ) + total_namespace_count = get_total_namespace_count(only_account_id=only_account_id) + print(f"{total_namespace_count=}\n") + def session_factory(): return global_session_scope() if dry_run else session_scope(None) - for account_id, message_ids in yield_account_id_and_message_ids( - only_account_id=only_account_id, - date_start=date_start, - date_end=date_end, - only_inbox=only_inbox, + for progress, (account_id, message_ids) in enumerate( + yield_account_id_and_message_ids( + only_account_id=only_account_id, + date_start=date_start, + date_end=date_end, + only_inbox=only_inbox, + ), + start=1, ): print(f"{account_id=}, {len(message_ids)=}") @@ -82,20 +99,19 @@ def session_factory(): for message_id in message_ids: with session_factory() as session: message = session.query(Message).get(message_id) - old_categories = set( - category.display_name for category in message.categories - ) + old_categories = set(category.name for category in message.categories) update_message_metadata( session, message.account, message, message.is_draft ) - new_categories = set( - category.display_name for category in message.categories - ) + new_categories = set(category.name for category in message.categories) if old_categories != new_categories: changed_counter += 1 - print(f"\t{message_id=}, {old_categories=} to {new_categories}=") + print( + f"\t{message.id=}, {message.message_id_header=}, {old_categories=} to {new_categories=}" + ) - print(f"{account_id=}, {changed_counter=}\n") + print(f"{account_id=}, {changed_counter=}") + print(f"{progress=}, {total_namespace_count=}\n") if __name__ == "__main__": From 9788625e016355aae5dc41377ac3ef09eedcfac8 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Wed, 13 Nov 2024 19:23:20 +0100 Subject: [PATCH 04/11] Skip empty categories --- bin/recategorize-messages.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index fc7d4e4f8..2e4f6abfa 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -99,11 +99,15 @@ def session_factory(): for message_id in message_ids: with session_factory() as session: message = session.query(Message).get(message_id) - old_categories = set(category.name for category in message.categories) + old_categories = set( + category.name for category in message.categories if category.name + ) update_message_metadata( session, message.account, message, message.is_draft ) - new_categories = set(category.name for category in message.categories) + new_categories = set( + category.name for category in message.categories if category.name + ) if old_categories != new_categories: changed_counter += 1 print( From b113206002c5c28093dce72f91c3b2355321cfa5 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Wed, 13 Nov 2024 20:06:09 +0100 Subject: [PATCH 05/11] only_types #notests --- bin/recategorize-messages.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index 2e4f6abfa..045c564d3 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -2,11 +2,13 @@ import datetime from collections.abc import Iterable +from typing import Literal import click from sqlalchemy.orm import Query from inbox.mailsync.backends.imap.common import update_message_metadata +from inbox.models.account import Account from inbox.models.backends.imap import ImapUid from inbox.models.folder import Folder from inbox.models.message import Message @@ -25,14 +27,24 @@ def get_total_namespace_count(*, only_account_id: int | None) -> int: return namespace_query.with_session(session).count() +AccountType = Literal["gmail", "generic", "outlook"] +ALL_ACCOUNT_TYPES = frozenset({"gmail", "generic", "outlook"}) + + def yield_account_id_and_message_ids( *, only_account_id: int | None, date_start: datetime.date | None, date_end: datetime.date | None, only_inbox: bool, + only_types: set[AccountType] = ALL_ACCOUNT_TYPES, ) -> Iterable[int, list[int]]: - namespace_query = Query([Namespace.account_id, Namespace.id]) + discriminators = {account_type + "account" for account_type in only_types} + namespace_query = ( + Query([Namespace.account_id, Namespace.id]) + .join(Namespace.account) + .filter(Account.discriminator.in_(discriminators)) + ) if only_account_id: namespace_query = namespace_query.filter( Namespace.account_id == only_account_id @@ -66,10 +78,12 @@ def yield_account_id_and_message_ids( @click.option("--date-end", type=click.DateTime(formats=["%Y-%m-%d"]), default=None) @click.option("--only-account-id", type=int, default=None) @click.option("--only-inbox", is_flag=True, default=False) +@click.option("--only-types", default=",".join(ALL_ACCOUNT_TYPES)) @click.option("--dry-run/--no-dry-run", default=True) def main( only_account_id: int | None, only_inbox: bool, + only_types: str, date_start: datetime.date | None, date_end: datetime.date | None, dry_run: bool, @@ -90,6 +104,7 @@ def session_factory(): date_start=date_start, date_end=date_end, only_inbox=only_inbox, + only_types=only_types.split(","), ), start=1, ): From dabffc67a1a2e75db6913c4e3b9808126ae5cc13 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Wed, 13 Nov 2024 20:18:04 +0100 Subject: [PATCH 06/11] only_types in total #notests --- bin/recategorize-messages.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index 045c564d3..a2e277cbd 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -15,9 +15,19 @@ from inbox.models.namespace import Namespace from inbox.models.session import global_session_scope, session_scope +AccountType = Literal["gmail", "generic", "outlook"] +ALL_ACCOUNT_TYPES = frozenset({"gmail", "generic", "outlook"}) -def get_total_namespace_count(*, only_account_id: int | None) -> int: - namespace_query = Query([Namespace.id]) + +def get_total_namespace_count( + *, only_account_id: int | None, only_types: set[AccountType] = ALL_ACCOUNT_TYPES +) -> int: + discriminators = {account_type + "account" for account_type in only_types} + namespace_query = ( + Query([Namespace]) + .join(Namespace.account) + .filter(Account.discriminator.in_(discriminators)) + ) if only_account_id: namespace_query = namespace_query.filter( Namespace.account_id == only_account_id @@ -27,10 +37,6 @@ def get_total_namespace_count(*, only_account_id: int | None) -> int: return namespace_query.with_session(session).count() -AccountType = Literal["gmail", "generic", "outlook"] -ALL_ACCOUNT_TYPES = frozenset({"gmail", "generic", "outlook"}) - - def yield_account_id_and_message_ids( *, only_account_id: int | None, @@ -92,7 +98,9 @@ def main( f"Settings: {only_account_id=}, {only_inbox=}, {date_start=}, {date_end=}, {dry_run=}\n" ) - total_namespace_count = get_total_namespace_count(only_account_id=only_account_id) + total_namespace_count = get_total_namespace_count( + only_account_id=only_account_id, only_types=set(only_types.split(",")) + ) print(f"{total_namespace_count=}\n") def session_factory(): From 91cf3efc83fdfd431ff8e3d7bf39d836135101e5 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Thu, 14 Nov 2024 10:18:29 +0100 Subject: [PATCH 07/11] Extract get_namespace_query --- bin/recategorize-messages.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index a2e277cbd..83e30fffe 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -19,12 +19,12 @@ ALL_ACCOUNT_TYPES = frozenset({"gmail", "generic", "outlook"}) -def get_total_namespace_count( - *, only_account_id: int | None, only_types: set[AccountType] = ALL_ACCOUNT_TYPES -) -> int: +def get_namespace_query( + entities: list, *, only_account_id: int | None, only_types: set[AccountType] +) -> Query: discriminators = {account_type + "account" for account_type in only_types} namespace_query = ( - Query([Namespace]) + Query(entities) .join(Namespace.account) .filter(Account.discriminator.in_(discriminators)) ) @@ -33,6 +33,16 @@ def get_total_namespace_count( Namespace.account_id == only_account_id ) + return namespace_query + + +def get_total_namespace_count( + *, only_account_id: int | None, only_types: set[AccountType] = ALL_ACCOUNT_TYPES +) -> int: + namespace_query = get_namespace_query( + [Namespace], only_account_id=only_account_id, only_types=only_types + ) + with global_session_scope() as session: return namespace_query.with_session(session).count() @@ -45,16 +55,11 @@ def yield_account_id_and_message_ids( only_inbox: bool, only_types: set[AccountType] = ALL_ACCOUNT_TYPES, ) -> Iterable[int, list[int]]: - discriminators = {account_type + "account" for account_type in only_types} - namespace_query = ( - Query([Namespace.account_id, Namespace.id]) - .join(Namespace.account) - .filter(Account.discriminator.in_(discriminators)) + namespace_query = get_namespace_query( + [Namespace.account_id, Namespace.id], + only_account_id=only_account_id, + only_types=only_types, ) - if only_account_id: - namespace_query = namespace_query.filter( - Namespace.account_id == only_account_id - ) with global_session_scope() as session: account_id_to_namespace_id = { From fe13a36ecbf7ddd2e7df267a97990b1a9ba12a03 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Thu, 14 Nov 2024 10:20:08 +0100 Subject: [PATCH 08/11] Make interval inclusive on both sides --- bin/recategorize-messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index 83e30fffe..a1b78fe0c 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -76,7 +76,7 @@ def yield_account_id_and_message_ids( if date_start: query = query.filter(Message.created_at >= date_start) if date_end: - query = query.filter(Message.created_at < date_end) + query = query.filter(Message.created_at <= date_end) with global_session_scope() as session: message_ids = [message_id for message_id, in query.with_session(session)] From e64a5f679035e2abaec36b604f6985990c4e33e8 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Thu, 14 Nov 2024 10:24:14 +0100 Subject: [PATCH 09/11] Only categories --- bin/recategorize-messages.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index a1b78fe0c..4b1cf8e0d 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -90,11 +90,13 @@ def yield_account_id_and_message_ids( @click.option("--only-account-id", type=int, default=None) @click.option("--only-inbox", is_flag=True, default=False) @click.option("--only-types", default=",".join(ALL_ACCOUNT_TYPES)) +@click.option("--only-categories", default=None) @click.option("--dry-run/--no-dry-run", default=True) def main( only_account_id: int | None, only_inbox: bool, only_types: str, + only_categories: str | None, date_start: datetime.date | None, date_end: datetime.date | None, dry_run: bool, @@ -137,6 +139,12 @@ def session_factory(): category.name for category in message.categories if category.name ) if old_categories != new_categories: + if only_categories and not new_categories & only_categories.split( + "," + ): + session.rollback() + continue + changed_counter += 1 print( f"\t{message.id=}, {message.message_id_header=}, {old_categories=} to {new_categories=}" From c7d6dc2ee890545f664bcdec2c10a0a5128cdaf3 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Thu, 14 Nov 2024 12:40:58 +0100 Subject: [PATCH 10/11] --only-account-ids --- bin/recategorize-messages.py | 41 +++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index 4b1cf8e0d..7692207e7 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -20,7 +20,10 @@ def get_namespace_query( - entities: list, *, only_account_id: int | None, only_types: set[AccountType] + entities: list, + *, + only_account_ids: Iterable[int] | None, + only_types: set[AccountType], ) -> Query: discriminators = {account_type + "account" for account_type in only_types} namespace_query = ( @@ -28,19 +31,21 @@ def get_namespace_query( .join(Namespace.account) .filter(Account.discriminator.in_(discriminators)) ) - if only_account_id: + if only_account_ids is not None: namespace_query = namespace_query.filter( - Namespace.account_id == only_account_id + Namespace.account_id.in_(only_account_ids) ) return namespace_query def get_total_namespace_count( - *, only_account_id: int | None, only_types: set[AccountType] = ALL_ACCOUNT_TYPES + *, + only_account_ids: Iterable[int] | None, + only_types: set[AccountType] = ALL_ACCOUNT_TYPES, ) -> int: namespace_query = get_namespace_query( - [Namespace], only_account_id=only_account_id, only_types=only_types + [Namespace], only_account_ids=only_account_ids, only_types=only_types ) with global_session_scope() as session: @@ -49,7 +54,7 @@ def get_total_namespace_count( def yield_account_id_and_message_ids( *, - only_account_id: int | None, + only_account_ids: Iterable[int] | None, date_start: datetime.date | None, date_end: datetime.date | None, only_inbox: bool, @@ -57,7 +62,7 @@ def yield_account_id_and_message_ids( ) -> Iterable[int, list[int]]: namespace_query = get_namespace_query( [Namespace.account_id, Namespace.id], - only_account_id=only_account_id, + only_account_ids=only_account_ids, only_types=only_types, ) @@ -84,16 +89,28 @@ def yield_account_id_and_message_ids( yield account_id, message_ids +def split_integers_separated_by_common( + ctx, param, comma_separated_value +) -> list[int] | None: + if comma_separated_value is not None: + return [int(value) for value in comma_separated_value.split(",")] + + @click.command() @click.option("--date-start", type=click.DateTime(formats=["%Y-%m-%d"]), default=None) @click.option("--date-end", type=click.DateTime(formats=["%Y-%m-%d"]), default=None) -@click.option("--only-account-id", type=int, default=None) +@click.option( + "--only-account-ids", + type=str, + default=None, + callback=split_integers_separated_by_common, +) @click.option("--only-inbox", is_flag=True, default=False) @click.option("--only-types", default=",".join(ALL_ACCOUNT_TYPES)) @click.option("--only-categories", default=None) @click.option("--dry-run/--no-dry-run", default=True) def main( - only_account_id: int | None, + only_account_ids: list[int] | None, only_inbox: bool, only_types: str, only_categories: str | None, @@ -102,11 +119,11 @@ def main( dry_run: bool, ) -> None: print( - f"Settings: {only_account_id=}, {only_inbox=}, {date_start=}, {date_end=}, {dry_run=}\n" + f"Settings: {only_account_ids=}, {only_inbox=}, {only_categories=}, {date_start=}, {date_end=}, {dry_run=}\n" ) total_namespace_count = get_total_namespace_count( - only_account_id=only_account_id, only_types=set(only_types.split(",")) + only_account_ids=only_account_ids, only_types=set(only_types.split(",")) ) print(f"{total_namespace_count=}\n") @@ -115,7 +132,7 @@ def session_factory(): for progress, (account_id, message_ids) in enumerate( yield_account_id_and_message_ids( - only_account_id=only_account_id, + only_account_ids=only_account_ids, date_start=date_start, date_end=date_end, only_inbox=only_inbox, From a0dfa3c2151b4bf2977fe83be1eccf653b0dba28 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Thu, 14 Nov 2024 13:10:14 +0100 Subject: [PATCH 11/11] Fix missing set --- bin/recategorize-messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/recategorize-messages.py b/bin/recategorize-messages.py index 7692207e7..e4e4d0663 100755 --- a/bin/recategorize-messages.py +++ b/bin/recategorize-messages.py @@ -156,8 +156,8 @@ def session_factory(): category.name for category in message.categories if category.name ) if old_categories != new_categories: - if only_categories and not new_categories & only_categories.split( - "," + if only_categories and not new_categories & set( + only_categories.split(",") ): session.rollback() continue