Chore: Enable ruff FBT (#8645)

This commit is contained in:
Sebastian Steinbeißer 2025-02-07 18:12:03 +01:00 committed by GitHub
parent b274665e21
commit e560fa3be0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 133 additions and 65 deletions

View File

@ -32,6 +32,7 @@ extend-select = [
"RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf "RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf
"FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly "FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly
"PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth "PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth
"FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt
] ]
ignore = ["DJ001", "SIM105", "RUF012"] ignore = ["DJ001", "SIM105", "RUF012"]

View File

@ -10,7 +10,7 @@ if TYPE_CHECKING:
class BulkArchiveStrategy: class BulkArchiveStrategy:
def __init__(self, zipf: ZipFile, follow_formatting: bool = False) -> None: def __init__(self, zipf: ZipFile, *, follow_formatting: bool = False) -> None:
self.zipf: ZipFile = zipf self.zipf: ZipFile = zipf
if follow_formatting: if follow_formatting:
self.make_unique_filename: Callable[..., Path | str] = ( self.make_unique_filename: Callable[..., Path | str] = (
@ -22,6 +22,7 @@ class BulkArchiveStrategy:
def _filename_only( def _filename_only(
self, self,
doc: Document, doc: Document,
*,
archive: bool = False, archive: bool = False,
folder: str = "", folder: str = "",
) -> str: ) -> str:
@ -33,7 +34,10 @@ class BulkArchiveStrategy:
""" """
counter = 0 counter = 0
while True: while True:
filename: str = folder + doc.get_public_filename(archive, counter) filename: str = folder + doc.get_public_filename(
archive=archive,
counter=counter,
)
if filename in self.zipf.namelist(): if filename in self.zipf.namelist():
counter += 1 counter += 1
else: else:
@ -42,6 +46,7 @@ class BulkArchiveStrategy:
def _formatted_filepath( def _formatted_filepath(
self, self,
doc: Document, doc: Document,
*,
archive: bool = False, archive: bool = False,
folder: str = "", folder: str = "",
) -> Path: ) -> Path:

View File

@ -245,6 +245,7 @@ def reprocess(doc_ids: list[int]) -> Literal["OK"]:
def set_permissions( def set_permissions(
doc_ids: list[int], doc_ids: list[int],
set_permissions, set_permissions,
*,
owner=None, owner=None,
merge=False, merge=False,
) -> Literal["OK"]: ) -> Literal["OK"]:
@ -309,6 +310,7 @@ def rotate(doc_ids: list[int], degrees: int) -> Literal["OK"]:
def merge( def merge(
doc_ids: list[int], doc_ids: list[int],
*,
metadata_document_id: int | None = None, metadata_document_id: int | None = None,
delete_originals: bool = False, delete_originals: bool = False,
user: User | None = None, user: User | None = None,
@ -387,6 +389,7 @@ def merge(
def split( def split(
doc_ids: list[int], doc_ids: list[int],
pages: list[list[int]], pages: list[list[int]],
*,
delete_originals: bool = False, delete_originals: bool = False,
user: User | None = None, user: User | None = None,
) -> Literal["OK"]: ) -> Literal["OK"]:

View File

@ -43,7 +43,7 @@ def delete_empty_directories(directory, root):
directory = os.path.normpath(os.path.dirname(directory)) directory = os.path.normpath(os.path.dirname(directory))
def generate_unique_filename(doc, archive_filename=False): def generate_unique_filename(doc, *, archive_filename=False):
""" """
Generates a unique filename for doc in settings.ORIGINALS_DIR. Generates a unique filename for doc in settings.ORIGINALS_DIR.
@ -77,7 +77,7 @@ def generate_unique_filename(doc, archive_filename=False):
while True: while True:
new_filename = generate_filename( new_filename = generate_filename(
doc, doc,
counter, counter=counter,
archive_filename=archive_filename, archive_filename=archive_filename,
) )
if new_filename == old_filename: if new_filename == old_filename:
@ -92,6 +92,7 @@ def generate_unique_filename(doc, archive_filename=False):
def generate_filename( def generate_filename(
doc: Document, doc: Document,
*,
counter=0, counter=0,
append_gpg=True, append_gpg=True,
archive_filename=False, archive_filename=False,

View File

@ -97,7 +97,7 @@ class StoragePathFilterSet(FilterSet):
class ObjectFilter(Filter): class ObjectFilter(Filter):
def __init__(self, exclude=False, in_list=False, field_name=""): def __init__(self, *, exclude=False, in_list=False, field_name=""):
super().__init__() super().__init__()
self.exclude = exclude self.exclude = exclude
self.in_list = in_list self.in_list = in_list

View File

@ -85,7 +85,7 @@ def get_schema() -> Schema:
) )
def open_index(recreate=False) -> FileIndex: def open_index(*, recreate=False) -> FileIndex:
try: try:
if exists_in(settings.INDEX_DIR) and not recreate: if exists_in(settings.INDEX_DIR) and not recreate:
return open_dir(settings.INDEX_DIR, schema=get_schema()) return open_dir(settings.INDEX_DIR, schema=get_schema())
@ -101,7 +101,7 @@ def open_index(recreate=False) -> FileIndex:
@contextmanager @contextmanager
def open_index_writer(optimize=False) -> AsyncWriter: def open_index_writer(*, optimize=False) -> AsyncWriter:
writer = AsyncWriter(open_index()) writer = AsyncWriter(open_index())
try: try:
@ -425,7 +425,7 @@ def autocomplete(
def get_permissions_criterias(user: User | None = None) -> list: def get_permissions_criterias(user: User | None = None) -> list:
user_criterias = [query.Term("has_owner", False)] user_criterias = [query.Term("has_owner", text=False)]
if user is not None: if user is not None:
if user.is_superuser: # superusers see all docs if user.is_superuser: # superusers see all docs
user_criterias = [] user_criterias = []

View File

@ -9,7 +9,7 @@ class Command(BaseCommand):
# This code is taken almost entirely from https://github.com/wagtail/wagtail/pull/11912 with all credit to the original author. # This code is taken almost entirely from https://github.com/wagtail/wagtail/pull/11912 with all credit to the original author.
help = "Converts UUID columns from char type to the native UUID type used in MariaDB 10.7+ and Django 5.0+." help = "Converts UUID columns from char type to the native UUID type used in MariaDB 10.7+ and Django 5.0+."
def convert_field(self, model, field_name, null=False): def convert_field(self, model, field_name, *, null=False):
if model._meta.get_field(field_name).model != model: # pragma: no cover if model._meta.get_field(field_name).model != model: # pragma: no cover
# Field is inherited from a parent model # Field is inherited from a parent model
return return

View File

@ -248,15 +248,15 @@ class Command(BaseCommand):
return return
if settings.CONSUMER_POLLING == 0 and INotify: if settings.CONSUMER_POLLING == 0 and INotify:
self.handle_inotify(directory, recursive, options["testing"]) self.handle_inotify(directory, recursive, is_testing=options["testing"])
else: else:
if INotify is None and settings.CONSUMER_POLLING == 0: # pragma: no cover if INotify is None and settings.CONSUMER_POLLING == 0: # pragma: no cover
logger.warning("Using polling as INotify import failed") logger.warning("Using polling as INotify import failed")
self.handle_polling(directory, recursive, options["testing"]) self.handle_polling(directory, recursive, is_testing=options["testing"])
logger.debug("Consumer exiting.") logger.debug("Consumer exiting.")
def handle_polling(self, directory, recursive, is_testing: bool): def handle_polling(self, directory, recursive, *, is_testing: bool):
logger.info(f"Polling directory for changes: {directory}") logger.info(f"Polling directory for changes: {directory}")
timeout = None timeout = None
@ -283,7 +283,7 @@ class Command(BaseCommand):
observer.stop() observer.stop()
observer.join() observer.join()
def handle_inotify(self, directory, recursive, is_testing: bool): def handle_inotify(self, directory, recursive, *, is_testing: bool):
logger.info(f"Using inotify to watch directory for changes: {directory}") logger.info(f"Using inotify to watch directory for changes: {directory}")
timeout_ms = None timeout_ms = None

View File

@ -84,7 +84,7 @@ def source_path(doc):
return os.path.join(settings.ORIGINALS_DIR, fname) return os.path.join(settings.ORIGINALS_DIR, fname)
def generate_unique_filename(doc, archive_filename=False): def generate_unique_filename(doc, *, archive_filename=False):
if archive_filename: if archive_filename:
old_filename = doc.archive_filename old_filename = doc.archive_filename
root = settings.ARCHIVE_DIR root = settings.ARCHIVE_DIR
@ -97,7 +97,7 @@ def generate_unique_filename(doc, archive_filename=False):
while True: while True:
new_filename = generate_filename( new_filename = generate_filename(
doc, doc,
counter, counter=counter,
archive_filename=archive_filename, archive_filename=archive_filename,
) )
if new_filename == old_filename: if new_filename == old_filename:
@ -110,7 +110,7 @@ def generate_unique_filename(doc, archive_filename=False):
return new_filename return new_filename
def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False): def generate_filename(doc, *, counter=0, append_gpg=True, archive_filename=False):
path = "" path = ""
try: try:

View File

@ -337,7 +337,7 @@ class Document(SoftDeleteModel, ModelWithOwner):
def archive_file(self): def archive_file(self):
return open(self.archive_path, "rb") return open(self.archive_path, "rb")
def get_public_filename(self, archive=False, counter=0, suffix=None) -> str: def get_public_filename(self, *, archive=False, counter=0, suffix=None) -> str:
""" """
Returns a sanitized filename for the document, not including any paths. Returns a sanitized filename for the document, not including any paths.
""" """

View File

@ -133,6 +133,7 @@ def get_parser_class_for_mime_type(mime_type: str) -> type["DocumentParser"] | N
def run_convert( def run_convert(
input_file, input_file,
output_file, output_file,
*,
density=None, density=None,
scale=None, scale=None,
alpha=None, alpha=None,

View File

@ -58,7 +58,7 @@ def get_groups_with_only_permission(obj, codename):
return Group.objects.filter(id__in=group_object_perm_group_ids).distinct() return Group.objects.filter(id__in=group_object_perm_group_ids).distinct()
def set_permissions_for_object(permissions: list[str], object, merge: bool = False): def set_permissions_for_object(permissions: list[str], object, *, merge: bool = False):
""" """
Set permissions for an object. The permissions are given as a list of strings Set permissions for an object. The permissions are given as a list of strings
in the format "action_modelname", e.g. "view_document". in the format "action_modelname", e.g. "view_document".

View File

@ -57,7 +57,7 @@ class SanityCheckFailedException(Exception):
pass pass
def check_sanity(progress=False) -> SanityCheckMessages: def check_sanity(*, progress=False) -> SanityCheckMessages:
messages = SanityCheckMessages() messages = SanityCheckMessages()
present_files = { present_files = {

View File

@ -85,6 +85,7 @@ def _suggestion_printer(
def set_correspondent( def set_correspondent(
sender, sender,
document: Document, document: Document,
*,
logging_group=None, logging_group=None,
classifier: DocumentClassifier | None = None, classifier: DocumentClassifier | None = None,
replace=False, replace=False,
@ -140,6 +141,7 @@ def set_correspondent(
def set_document_type( def set_document_type(
sender, sender,
document: Document, document: Document,
*,
logging_group=None, logging_group=None,
classifier: DocumentClassifier | None = None, classifier: DocumentClassifier | None = None,
replace=False, replace=False,
@ -196,6 +198,7 @@ def set_document_type(
def set_tags( def set_tags(
sender, sender,
document: Document, document: Document,
*,
logging_group=None, logging_group=None,
classifier: DocumentClassifier | None = None, classifier: DocumentClassifier | None = None,
replace=False, replace=False,
@ -251,6 +254,7 @@ def set_tags(
def set_storage_path( def set_storage_path(
sender, sender,
document: Document, document: Document,
*,
logging_group=None, logging_group=None,
classifier: DocumentClassifier | None = None, classifier: DocumentClassifier | None = None,
replace=False, replace=False,

View File

@ -63,7 +63,7 @@ def index_optimize():
writer.commit(optimize=True) writer.commit(optimize=True)
def index_reindex(progress_bar_disable=False): def index_reindex(*, progress_bar_disable=False):
documents = Document.objects.all() documents = Document.objects.all()
ix = index.open_index(recreate=True) ix = index.open_index(recreate=True)

View File

@ -165,6 +165,7 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
self, self,
query: list, query: list,
reference_predicate: Callable[[DocumentWrapper], bool], reference_predicate: Callable[[DocumentWrapper], bool],
*,
match_nothing_ok=False, match_nothing_ok=False,
): ):
""" """

View File

@ -535,7 +535,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
metadata_document_id = self.doc1.id metadata_document_id = self.doc1.id
user = User.objects.create(username="test_user") user = User.objects.create(username="test_user")
result = bulk_edit.merge(doc_ids, None, False, user) result = bulk_edit.merge(
doc_ids,
metadata_document_id=None,
delete_originals=False,
user=user,
)
expected_filename = ( expected_filename = (
f"{'_'.join([str(doc_id) for doc_id in doc_ids])[:100]}_merged.pdf" f"{'_'.join([str(doc_id) for doc_id in doc_ids])[:100]}_merged.pdf"
@ -638,7 +643,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
doc_ids = [self.doc2.id] doc_ids = [self.doc2.id]
pages = [[1, 2], [3]] pages = [[1, 2], [3]]
user = User.objects.create(username="test_user") user = User.objects.create(username="test_user")
result = bulk_edit.split(doc_ids, pages, False, user) result = bulk_edit.split(doc_ids, pages, delete_originals=False, user=user)
self.assertEqual(mock_consume_file.call_count, 2) self.assertEqual(mock_consume_file.call_count, 2)
consume_file_args, _ = mock_consume_file.call_args consume_file_args, _ = mock_consume_file.call_args
self.assertEqual(consume_file_args[1].title, "B (split 2)") self.assertEqual(consume_file_args[1].title, "B (split 2)")

View File

@ -233,7 +233,7 @@ class FaultyGenericExceptionParser(_BaseTestParser):
raise Exception("Generic exception.") raise Exception("Generic exception.")
def fake_magic_from_file(file, mime=False): def fake_magic_from_file(file, *, mime=False):
if mime: if mime:
if file.name.startswith("invalid_pdf"): if file.name.startswith("invalid_pdf"):
return "application/octet-stream" return "application/octet-stream"

View File

@ -10,7 +10,7 @@ class TestDelayedQuery(TestCase):
super().setUp() super().setUp()
# all tests run without permission criteria, so has_no_owner query will always # all tests run without permission criteria, so has_no_owner query will always
# be appended. # be appended.
self.has_no_owner = query.Or([query.Term("has_owner", False)]) self.has_no_owner = query.Or([query.Term("has_owner", text=False)])
def _get_testset__id__in(self, param, field): def _get_testset__id__in(self, param, field):
return ( return (
@ -43,12 +43,12 @@ class TestDelayedQuery(TestCase):
def test_get_permission_criteria(self): def test_get_permission_criteria(self):
# tests contains tuples of user instances and the expected filter # tests contains tuples of user instances and the expected filter
tests = ( tests = (
(None, [query.Term("has_owner", False)]), (None, [query.Term("has_owner", text=False)]),
(User(42, username="foo", is_superuser=True), []), (User(42, username="foo", is_superuser=True), []),
( (
User(42, username="foo", is_superuser=False), User(42, username="foo", is_superuser=False),
[ [
query.Term("has_owner", False), query.Term("has_owner", text=False),
query.Term("owner_id", 42), query.Term("owner_id", 42),
query.Term("viewer_id", "42"), query.Term("viewer_id", "42"),
], ],

View File

@ -93,7 +93,7 @@ class ConsumerThreadMixin(DocumentConsumeDelayMixin):
else: else:
print("Consumed a perfectly valid file.") # noqa: T201 print("Consumed a perfectly valid file.") # noqa: T201
def slow_write_file(self, target, incomplete=False): def slow_write_file(self, target, *, incomplete=False):
with open(self.sample_file, "rb") as f: with open(self.sample_file, "rb") as f:
pdf_bytes = f.read() pdf_bytes = f.read()

View File

@ -188,7 +188,7 @@ class TestExportImport(
return manifest return manifest
def test_exporter(self, use_filename_format=False): def test_exporter(self, *, use_filename_format=False):
shutil.rmtree(os.path.join(self.dirs.media_dir, "documents")) shutil.rmtree(os.path.join(self.dirs.media_dir, "documents"))
shutil.copytree( shutil.copytree(
os.path.join(os.path.dirname(__file__), "samples", "documents"), os.path.join(os.path.dirname(__file__), "samples", "documents"),

View File

@ -23,6 +23,7 @@ class _TestMatchingBase(TestCase):
match_algorithm: str, match_algorithm: str,
should_match: Iterable[str], should_match: Iterable[str],
no_match: Iterable[str], no_match: Iterable[str],
*,
case_sensitive: bool = False, case_sensitive: bool = False,
): ):
for klass in (Tag, Correspondent, DocumentType): for klass in (Tag, Correspondent, DocumentType):

View File

@ -1608,7 +1608,7 @@ class BulkDownloadView(GenericAPIView):
strategy_class = ArchiveOnlyStrategy strategy_class = ArchiveOnlyStrategy
with zipfile.ZipFile(temp.name, "w", compression) as zipf: with zipfile.ZipFile(temp.name, "w", compression) as zipf:
strategy = strategy_class(zipf, follow_filename_format) strategy = strategy_class(zipf, follow_formatting=follow_filename_format)
for document in documents: for document in documents:
strategy.add_document(document) strategy.add_document(document)
@ -1872,7 +1872,7 @@ class SharedLinkView(View):
) )
def serve_file(doc: Document, use_archive: bool, disposition: str): def serve_file(*, doc: Document, use_archive: bool, disposition: str):
if use_archive: if use_archive:
file_handle = doc.archive_file file_handle = doc.archive_file
filename = doc.get_public_filename(archive=True) filename = doc.get_public_filename(archive=True)

View File

@ -148,7 +148,7 @@ class UserViewSet(ModelViewSet):
).first() ).first()
if authenticator is not None: if authenticator is not None:
delete_and_cleanup(request, authenticator) delete_and_cleanup(request, authenticator)
return Response(True) return Response(data=True)
else: else:
return HttpResponseNotFound("TOTP not found") return HttpResponseNotFound("TOTP not found")
@ -262,7 +262,7 @@ class TOTPView(GenericAPIView):
).first() ).first()
if authenticator is not None: if authenticator is not None:
delete_and_cleanup(request, authenticator) delete_and_cleanup(request, authenticator)
return Response(True) return Response(data=True)
else: else:
return HttpResponseNotFound("TOTP not found") return HttpResponseNotFound("TOTP not found")

View File

@ -121,7 +121,7 @@ class MarkReadMailAction(BaseMailAction):
return {"seen": False} return {"seen": False}
def post_consume(self, M: MailBox, message_uid: str, parameter: str): def post_consume(self, M: MailBox, message_uid: str, parameter: str):
M.flag(message_uid, [MailMessageFlags.SEEN], True) M.flag(message_uid, [MailMessageFlags.SEEN], value=True)
class MoveMailAction(BaseMailAction): class MoveMailAction(BaseMailAction):
@ -142,7 +142,7 @@ class FlagMailAction(BaseMailAction):
return {"flagged": False} return {"flagged": False}
def post_consume(self, M: MailBox, message_uid: str, parameter: str): def post_consume(self, M: MailBox, message_uid: str, parameter: str):
M.flag(message_uid, [MailMessageFlags.FLAGGED], True) M.flag(message_uid, [MailMessageFlags.FLAGGED], value=True)
class TagMailAction(BaseMailAction): class TagMailAction(BaseMailAction):
@ -150,7 +150,7 @@ class TagMailAction(BaseMailAction):
A mail action that tags mails after processing. A mail action that tags mails after processing.
""" """
def __init__(self, parameter: str, supports_gmail_labels: bool): def __init__(self, parameter: str, *, supports_gmail_labels: bool):
# The custom tag should look like "apple:<color>" # The custom tag should look like "apple:<color>"
if "apple:" in parameter.lower(): if "apple:" in parameter.lower():
_, self.color = parameter.split(":") _, self.color = parameter.split(":")
@ -188,19 +188,19 @@ class TagMailAction(BaseMailAction):
M.flag( M.flag(
message_uid, message_uid,
set(itertools.chain(*APPLE_MAIL_TAG_COLORS.values())), set(itertools.chain(*APPLE_MAIL_TAG_COLORS.values())),
False, value=False,
) )
# Set new $MailFlagBits # Set new $MailFlagBits
M.flag(message_uid, APPLE_MAIL_TAG_COLORS.get(self.color), True) M.flag(message_uid, APPLE_MAIL_TAG_COLORS.get(self.color), value=True)
# Set the general \Flagged # Set the general \Flagged
# This defaults to the "red" flag in AppleMail and # This defaults to the "red" flag in AppleMail and
# "stars" in Thunderbird or GMail # "stars" in Thunderbird or GMail
M.flag(message_uid, [MailMessageFlags.FLAGGED], True) M.flag(message_uid, [MailMessageFlags.FLAGGED], value=True)
elif self.keyword: elif self.keyword:
M.flag(message_uid, [self.keyword], True) M.flag(message_uid, [self.keyword], value=True)
else: else:
raise MailError("No keyword specified.") raise MailError("No keyword specified.")
@ -268,7 +268,7 @@ def apply_mail_action(
mailbox_login(M, account) mailbox_login(M, account)
M.folder.set(rule.folder) M.folder.set(rule.folder)
action = get_rule_action(rule, supports_gmail_labels) action = get_rule_action(rule, supports_gmail_labels=supports_gmail_labels)
try: try:
action.post_consume(M, message_uid, rule.action_parameter) action.post_consume(M, message_uid, rule.action_parameter)
except errors.ImapToolsError: except errors.ImapToolsError:
@ -356,7 +356,7 @@ def queue_consumption_tasks(
).delay() ).delay()
def get_rule_action(rule: MailRule, supports_gmail_labels: bool) -> BaseMailAction: def get_rule_action(rule: MailRule, *, supports_gmail_labels: bool) -> BaseMailAction:
""" """
Returns a BaseMailAction instance for the given rule. Returns a BaseMailAction instance for the given rule.
""" """
@ -370,12 +370,15 @@ def get_rule_action(rule: MailRule, supports_gmail_labels: bool) -> BaseMailActi
elif rule.action == MailRule.MailAction.MARK_READ: elif rule.action == MailRule.MailAction.MARK_READ:
return MarkReadMailAction() return MarkReadMailAction()
elif rule.action == MailRule.MailAction.TAG: elif rule.action == MailRule.MailAction.TAG:
return TagMailAction(rule.action_parameter, supports_gmail_labels) return TagMailAction(
rule.action_parameter,
supports_gmail_labels=supports_gmail_labels,
)
else: else:
raise NotImplementedError("Unknown action.") # pragma: no cover raise NotImplementedError("Unknown action.") # pragma: no cover
def make_criterias(rule: MailRule, supports_gmail_labels: bool): def make_criterias(rule: MailRule, *, supports_gmail_labels: bool):
""" """
Returns criteria to be applied to MailBox.fetch for the given rule. Returns criteria to be applied to MailBox.fetch for the given rule.
""" """
@ -393,7 +396,10 @@ def make_criterias(rule: MailRule, supports_gmail_labels: bool):
if rule.filter_body: if rule.filter_body:
criterias["body"] = rule.filter_body criterias["body"] = rule.filter_body
rule_query = get_rule_action(rule, supports_gmail_labels).get_criteria() rule_query = get_rule_action(
rule,
supports_gmail_labels=supports_gmail_labels,
).get_criteria()
if isinstance(rule_query, dict): if isinstance(rule_query, dict):
if len(rule_query) or len(criterias): if len(rule_query) or len(criterias):
return AND(**rule_query, **criterias) return AND(**rule_query, **criterias)
@ -563,7 +569,7 @@ class MailAccountHandler(LoggingMixin):
total_processed_files += self._handle_mail_rule( total_processed_files += self._handle_mail_rule(
M, M,
rule, rule,
supports_gmail_labels, supports_gmail_labels=supports_gmail_labels,
) )
except Exception as e: except Exception as e:
self.log.exception( self.log.exception(
@ -588,6 +594,7 @@ class MailAccountHandler(LoggingMixin):
self, self,
M: MailBox, M: MailBox,
rule: MailRule, rule: MailRule,
*,
supports_gmail_labels: bool, supports_gmail_labels: bool,
): ):
folders = [rule.folder] folders = [rule.folder]
@ -616,7 +623,7 @@ class MailAccountHandler(LoggingMixin):
f"does not exist in account {rule.account}", f"does not exist in account {rule.account}",
) from err ) from err
criterias = make_criterias(rule, supports_gmail_labels) criterias = make_criterias(rule, supports_gmail_labels=supports_gmail_labels)
self.log.debug( self.log.debug(
f"Rule {rule}: Searching folder with criteria {criterias}", f"Rule {rule}: Searching folder with criteria {criterias}",

View File

@ -124,7 +124,7 @@ class BogusMailBox(AbstractContextManager):
if username != self.USERNAME or access_token != self.ACCESS_TOKEN: if username != self.USERNAME or access_token != self.ACCESS_TOKEN:
raise MailboxLoginError("BAD", "OK") raise MailboxLoginError("BAD", "OK")
def fetch(self, criteria, mark_seen, charset="", bulk=True): def fetch(self, criteria, mark_seen, charset="", *, bulk=True):
msg = self.messages msg = self.messages
criteria = str(criteria).strip("()").split(" ") criteria = str(criteria).strip("()").split(" ")
@ -190,7 +190,7 @@ class BogusMailBox(AbstractContextManager):
raise Exception raise Exception
def fake_magic_from_buffer(buffer, mime=False): def fake_magic_from_buffer(buffer, *, mime=False):
if mime: if mime:
if "PDF" in str(buffer): if "PDF" in str(buffer):
return "application/pdf" return "application/pdf"
@ -206,6 +206,7 @@ class MessageBuilder:
def create_message( def create_message(
self, self,
*,
attachments: int | list[_AttachmentDef] = 1, attachments: int | list[_AttachmentDef] = 1,
body: str = "", body: str = "",
subject: str = "the subject", subject: str = "the subject",
@ -783,12 +784,18 @@ class TestMail(
) )
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)),
2,
)
self.mail_account_handler.handle_mail_account(account) self.mail_account_handler.handle_mail_account(account)
self.mailMocker.apply_mail_actions() self.mailMocker.apply_mail_actions()
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 0) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)),
0,
)
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
def test_handle_mail_account_delete(self): def test_handle_mail_account_delete(self):
@ -853,7 +860,7 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
self.assertEqual( self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", False)), len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", mark_seen=False)),
2, 2,
) )
@ -861,7 +868,7 @@ class TestMail(
self.mailMocker.apply_mail_actions() self.mailMocker.apply_mail_actions()
self.assertEqual( self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", False)), len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", mark_seen=False)),
1, 1,
) )
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
@ -934,7 +941,12 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
self.assertEqual( self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNKEYWORD processed", False)), len(
self.mailMocker.bogus_mailbox.fetch(
"UNKEYWORD processed",
mark_seen=False,
),
),
2, 2,
) )
@ -943,7 +955,12 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
self.assertEqual( self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNKEYWORD processed", False)), len(
self.mailMocker.bogus_mailbox.fetch(
"UNKEYWORD processed",
mark_seen=False,
),
),
0, 0,
) )
@ -967,12 +984,18 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
criteria = NOT(gmail_label="processed") criteria = NOT(gmail_label="processed")
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch(criteria, False)), 2) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch(criteria, mark_seen=False)),
2,
)
self.mail_account_handler.handle_mail_account(account) self.mail_account_handler.handle_mail_account(account)
self.mailMocker.apply_mail_actions() self.mailMocker.apply_mail_actions()
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch(criteria, False)), 0) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch(criteria, mark_seen=False)),
0,
)
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
def test_tag_mail_action_applemail_wrong_input(self): def test_tag_mail_action_applemail_wrong_input(self):
@ -980,7 +1003,7 @@ class TestMail(
MailError, MailError,
TagMailAction, TagMailAction,
"apple:black", "apple:black",
False, supports_gmail_labels=False,
) )
def test_handle_mail_account_tag_applemail(self): def test_handle_mail_account_tag_applemail(self):
@ -1002,7 +1025,7 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
self.assertEqual( self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", False)), len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", mark_seen=False)),
2, 2,
) )
@ -1010,7 +1033,7 @@ class TestMail(
self.mailMocker.apply_mail_actions() self.mailMocker.apply_mail_actions()
self.assertEqual( self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", False)), len(self.mailMocker.bogus_mailbox.fetch("UNFLAGGED", mark_seen=False)),
0, 0,
) )
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
@ -1324,13 +1347,19 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
self.mailMocker._queue_consumption_tasks_mock.assert_not_called() self.mailMocker._queue_consumption_tasks_mock.assert_not_called()
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)),
2,
)
self.mail_account_handler.handle_mail_account(account) self.mail_account_handler.handle_mail_account(account)
self.mailMocker.apply_mail_actions() self.mailMocker.apply_mail_actions()
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 2) self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 2)
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 0) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)),
0,
)
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
def test_auth_plain_fallback_fails_still(self): def test_auth_plain_fallback_fails_still(self):
@ -1390,13 +1419,19 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 0) self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 0)
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)),
2,
)
self.mail_account_handler.handle_mail_account(account) self.mail_account_handler.handle_mail_account(account)
self.mailMocker.apply_mail_actions() self.mailMocker.apply_mail_actions()
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 2) self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 2)
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 0) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)),
0,
)
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
def test_disabled_rule(self): def test_disabled_rule(self):
@ -1425,12 +1460,15 @@ class TestMail(
self.mailMocker.apply_mail_actions() self.mailMocker.apply_mail_actions()
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3)
self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)),
2,
)
self.mail_account_handler.handle_mail_account(account) self.mail_account_handler.handle_mail_account(account)
self.mailMocker.apply_mail_actions() self.mailMocker.apply_mail_actions()
self.assertEqual( self.assertEqual(
len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", mark_seen=False)),
2, 2,
) # still 2 ) # still 2

View File

@ -214,6 +214,7 @@ class RasterisedDocumentParser(DocumentParser):
mime_type, mime_type,
output_file, output_file,
sidecar_file, sidecar_file,
*,
safe_fallback=False, safe_fallback=False,
): ):
if TYPE_CHECKING: if TYPE_CHECKING: