mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-25 13:24:57 +00:00
perf: use imap_tools uid_list fetch to skip manual UID batching
imap_tools 1.14.0 adds a uid_list fetch arg that fetches by UID directly without issuing a SEARCH command, and does its own bulk-fetch batching. Use it in place of the manual per-batch AND(uid=...) fetch loop.
This commit is contained in:
+1
-1
@@ -47,7 +47,7 @@ dependencies = [
|
||||
"gotenberg-client~=0.14.0",
|
||||
"httpx-oauth~=0.16",
|
||||
"ijson>=3.2",
|
||||
"imap-tools~=1.13.0",
|
||||
"imap-tools~=1.14.0",
|
||||
"jinja2~=3.1.5",
|
||||
"langdetect~=1.0.9",
|
||||
"llama-index-core>=0.14.22",
|
||||
|
||||
+10
-19
@@ -712,25 +712,16 @@ class MailAccountHandler(LoggingMixin):
|
||||
return 0
|
||||
|
||||
sorted_new_uids = sorted(new_uids, key=int)
|
||||
message_batches = []
|
||||
# ikvk/imap_tools#268 requests a direct UID-list fetch that would let us drop this manual batching loop
|
||||
for batch_start in range(0, len(sorted_new_uids), MAIL_FETCH_BATCH_SIZE):
|
||||
batch = sorted_new_uids[batch_start : batch_start + MAIL_FETCH_BATCH_SIZE]
|
||||
try:
|
||||
message_batches.append(
|
||||
M.fetch(
|
||||
criteria=AND(uid=batch),
|
||||
mark_seen=False,
|
||||
charset=rule.account.character_set,
|
||||
bulk=True,
|
||||
),
|
||||
)
|
||||
except Exception as err:
|
||||
raise MailError(
|
||||
f"Rule {rule}: Error while fetching folder {rule.folder}",
|
||||
) from err
|
||||
|
||||
messages = itertools.chain(*message_batches)
|
||||
try:
|
||||
messages = M.fetch(
|
||||
uid_list=sorted_new_uids,
|
||||
mark_seen=False,
|
||||
bulk=MAIL_FETCH_BATCH_SIZE,
|
||||
)
|
||||
except Exception as err:
|
||||
raise MailError(
|
||||
f"Rule {rule}: Error while fetching folder {rule.folder}",
|
||||
) from err
|
||||
|
||||
mails_processed = 0
|
||||
total_processed_files = 0
|
||||
|
||||
@@ -134,7 +134,17 @@ class BogusMailBox(AbstractContextManager):
|
||||
if username != self.USERNAME or access_token != self.ACCESS_TOKEN:
|
||||
raise MailboxLoginError("BAD", "OK")
|
||||
|
||||
def fetch(self, criteria, mark_seen, charset="", *, bulk=True):
|
||||
def fetch(
|
||||
self,
|
||||
criteria="ALL",
|
||||
charset="",
|
||||
*,
|
||||
mark_seen=True,
|
||||
bulk=True,
|
||||
uid_list=None,
|
||||
):
|
||||
if uid_list is not None:
|
||||
return [m for m in self.messages if m.uid in uid_list]
|
||||
return self._filter_messages(criteria)
|
||||
|
||||
def uids(self, criteria, charset="") -> list[str]:
|
||||
@@ -416,7 +426,7 @@ def assert_eventually_equals(
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
if getter_fn() == expected_value:
|
||||
return None
|
||||
return
|
||||
time.sleep(interval)
|
||||
actual = getter_fn()
|
||||
raise AssertionError(f"Expected {expected_value}, but got {actual}")
|
||||
@@ -443,7 +453,8 @@ class TestMail(
|
||||
WHEN:
|
||||
- The mail account is processed
|
||||
THEN:
|
||||
- The body fetch is issued in multiple batches
|
||||
- The body fetch is issued once, with all UIDs and the configured batch size
|
||||
handed to imap_tools so it can bulk-fetch in batches server-side
|
||||
- Every message is still processed (none dropped at a batch boundary)
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
@@ -476,8 +487,11 @@ class TestMail(
|
||||
) as fetch_spy:
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
# ceil(12 / 5) == 3 batches
|
||||
self.assertEqual(fetch_spy.call_count, 3)
|
||||
# A single fetch() call hands the full UID list and batch size to imap_tools,
|
||||
# which does its own bulk-fetching in batches of MAIL_FETCH_BATCH_SIZE.
|
||||
fetch_spy.assert_called_once()
|
||||
self.assertEqual(fetch_spy.call_args.kwargs["bulk"], 5)
|
||||
self.assertEqual(len(fetch_spy.call_args.kwargs["uid_list"]), message_count)
|
||||
self.assertEqual(
|
||||
ProcessedMail.objects.filter(rule=rule).count(),
|
||||
message_count,
|
||||
@@ -1673,7 +1687,7 @@ class TestMail(
|
||||
if message.from_ == "amazon@amazon.de":
|
||||
raise ValueError("Does not compute.")
|
||||
else:
|
||||
return None
|
||||
return
|
||||
|
||||
m.side_effect = get_correspondent_fake
|
||||
|
||||
|
||||
Reference in New Issue
Block a user