diff --git a/pyproject.toml b/pyproject.toml index 68e8034cb..db2c09430 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -185,6 +185,7 @@ line-ending = "lf" [tool.ruff.lint] # https://docs.astral.sh/ruff/rules/ extend-select = [ + "B", # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b "COM", # https://docs.astral.sh/ruff/rules/#flake8-commas-com "DJ", # https://docs.astral.sh/ruff/rules/#flake8-django-dj "EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe diff --git a/src/documents/double_sided.py b/src/documents/double_sided.py index 3c3ec4723..eb2f6aa84 100644 --- a/src/documents/double_sided.py +++ b/src/documents/double_sided.py @@ -99,7 +99,7 @@ class CollatePlugin(NoCleanupPluginMixin, NoSetupPluginMixin, ConsumeTaskPlugin) "two uploaded files don't belong to the same double-" "sided scan. Please retry, starting with the odd " "numbered pages again.", - ) + ) from None # Merged file has the same path, but without the # double-sided subdir. Therefore, it is also in the # consumption dir and will be picked up for processing diff --git a/src/documents/filters.py b/src/documents/filters.py index ddc784204..bfb18cf2f 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -350,7 +350,7 @@ def handle_validation_prefix(func: Callable): try: return func(*args, **kwargs) except serializers.ValidationError as e: - raise serializers.ValidationError({validation_prefix: e.detail}) + raise serializers.ValidationError({validation_prefix: e.detail}) from e # Update the signature to include the validation_prefix argument old_sig = inspect.signature(func) @@ -461,7 +461,7 @@ class CustomFieldQueryParser: except json.JSONDecodeError: raise serializers.ValidationError( {self._validation_prefix: [_("Value must be valid JSON.")]}, - ) + ) from None return ( self._parse_expr(expr, validation_prefix=self._validation_prefix), self._annotations, @@ -589,7 +589,7 @@ class CustomFieldQueryParser: except CustomField.DoesNotExist: raise serializers.ValidationError( [_("{name!r} is not a valid custom field.").format(name=id_or_name)], - ) + ) from None self._custom_fields[custom_field.id] = custom_field self._custom_fields[custom_field.name] = custom_field return custom_field @@ -988,7 +988,7 @@ class DocumentsOrderingFilter(OrderingFilter): except CustomField.DoesNotExist: raise serializers.ValidationError( {self.prefix + str(custom_field_id): [_("Custom field not found")]}, - ) + ) from None annotation = None match field.data_type: diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index b89faf7d9..d66db3062 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -480,7 +480,7 @@ class Command(CryptMixin, PaperlessCommand): } # 3. Export files from each document - for index, document_dict in enumerate( + for _, document_dict in enumerate( self.track( document_manifest, description="Exporting documents...", diff --git a/src/documents/models.py b/src/documents/models.py index a77447512..3f14e6f07 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -369,7 +369,7 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager- If the queryset already annotated ``effective_content``, that value is used. """ if hasattr(self, "effective_content"): - return getattr(self, "effective_content") + return self.effective_content if self.root_document_id is not None or self.pk is None: return self.content @@ -1204,8 +1204,8 @@ class CustomFieldInstance(SoftDeleteModel): def get_value_field_name(cls, data_type: CustomField.FieldDataType): try: return cls.TYPE_TO_DATA_STORE_NAME_MAP[data_type] - except KeyError: # pragma: no cover - raise NotImplementedError(data_type) + except KeyError as exc: # pragma: no cover + raise NotImplementedError(data_type) from exc @property def value(self): diff --git a/src/documents/plugins/date_parsing/base.py b/src/documents/plugins/date_parsing/base.py index fd45ceec8..bfe1d4ee8 100644 --- a/src/documents/plugins/date_parsing/base.py +++ b/src/documents/plugins/date_parsing/base.py @@ -67,8 +67,7 @@ class DateParserPluginBase(ABC): Subclasses can override this to release resources. """ - # Default implementation does nothing. - # Returning None implies exceptions are propagated. + return None def _parse_string( self, diff --git a/src/documents/search/_backend.py b/src/documents/search/_backend.py index 5b5c8aa08..f805dec0c 100644 --- a/src/documents/search/_backend.py +++ b/src/documents/search/_backend.py @@ -195,12 +195,12 @@ class WriteBatch: try: self._lock.acquire(timeout=self._lock_timeout) break - except filelock.Timeout: + except filelock.Timeout as exc: if attempt == _LOCK_RETRY_ATTEMPTS - 1: raise SearchIndexLockError( f"Could not acquire index lock after {_LOCK_RETRY_ATTEMPTS} " f"attempts (timeout={self._lock_timeout}s each)", - ) + ) from exc sleep_s = random.uniform( 0, min(_LOCK_BACKOFF_CAP, _LOCK_BACKOFF_BASE * (2**attempt)), @@ -651,7 +651,11 @@ class TantivyBackend: result_ids = cast("list[int]", searcher.fast_field_values("id", result_addrs)) addr_by_id: dict[int, tuple[float, tantivy.DocAddress]] = { doc_id: (score, addr) - for (score, addr), doc_id in zip(batch_results.hits, result_ids) + for (score, addr), doc_id in zip( + batch_results.hits, + result_ids, + strict=False, + ) } snippet_generator = None diff --git a/src/documents/search/_query.py b/src/documents/search/_query.py index 932d68bc1..eb4e429c8 100644 --- a/src/documents/search/_query.py +++ b/src/documents/search/_query.py @@ -270,7 +270,7 @@ def _rewrite_compact_date(query: str) -> str: except TimeoutError: # pragma: no cover raise ValueError( "Query too complex to process (compact date rewrite timed out)", - ) + ) from None def _rewrite_relative_range(query: str) -> str: @@ -303,7 +303,7 @@ def _rewrite_relative_range(query: str) -> str: except TimeoutError: # pragma: no cover raise ValueError( "Query too complex to process (relative range rewrite timed out)", - ) + ) from None def _rewrite_whoosh_relative_range(query: str) -> str: @@ -334,7 +334,7 @@ def _rewrite_whoosh_relative_range(query: str) -> str: except TimeoutError: # pragma: no cover raise ValueError( "Query too complex to process (Whoosh relative range rewrite timed out)", - ) + ) from None def _rewrite_8digit_date(query: str, tz: tzinfo) -> str: @@ -376,7 +376,7 @@ def _rewrite_8digit_date(query: str, tz: tzinfo) -> str: except TimeoutError: # pragma: no cover raise ValueError( "Query too complex to process (8-digit date rewrite timed out)", - ) + ) from None def _rewrite_year_range(query: str) -> str: @@ -401,7 +401,9 @@ def _rewrite_year_range(query: str) -> str: try: return _YEAR_RANGE_RE.sub(_sub, query, timeout=_REGEX_TIMEOUT) except TimeoutError: # pragma: no cover - raise ValueError("Query too complex to process (year range rewrite timed out)") + raise ValueError( + "Query too complex to process (year range rewrite timed out)", + ) from None def rewrite_natural_date_keywords(query: str, tz: tzinfo) -> str: @@ -443,7 +445,7 @@ def rewrite_natural_date_keywords(query: str, tz: tzinfo) -> str: except TimeoutError: # pragma: no cover raise ValueError( "Query too complex to process (date keyword rewrite timed out)", - ) + ) from None def normalize_query(query: str) -> str: @@ -483,7 +485,9 @@ def normalize_query(query: str) -> str: query = _SPACED_OPERATOR_RE.sub(" ", query, timeout=_REGEX_TIMEOUT).strip() return query except TimeoutError: # pragma: no cover - raise ValueError("Query too complex to process (normalization timed out)") + raise ValueError( + "Query too complex to process (normalization timed out)", + ) from None def build_permission_filter( diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 1ff3798db..0671ed1f1 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -163,7 +163,7 @@ class MatchingModelSerializer(serializers.ModelSerializer[Any]): logger.debug(f"Invalid regular expression: {e!s}") raise serializers.ValidationError( "Invalid regular expression, see log for details.", - ) + ) from None return match @@ -867,7 +867,9 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer[CustomFieldInsta try: value_int = int(data["value"]) except (TypeError, ValueError): - raise serializers.ValidationError("Enter a valid integer.") + raise serializers.ValidationError( + "Enter a valid integer.", + ) from None # Keep values within the PostgreSQL integer range MinValueValidator(-2147483648)(value_int) MaxValueValidator(2147483647)(value_int) @@ -899,7 +901,7 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer[CustomFieldInsta except Exception: raise serializers.ValidationError( f"Value must be an id of an element in {select_options}", - ) + ) from None elif field.data_type == CustomField.FieldDataType.DOCUMENTLINK: if not (isinstance(data["value"], list) or data["value"] is None): raise serializers.ValidationError( @@ -1090,7 +1092,7 @@ class DocumentSerializer( def to_representation(self, instance): doc = super().to_representation(instance) if "content" in self.fields and hasattr(instance, "effective_content"): - doc["content"] = getattr(instance, "effective_content") or "" + doc["content"] = instance.effective_content or "" if self.truncate_content and "content" in self.fields: doc["content"] = doc.get("content")[0:550] return doc @@ -1452,7 +1454,7 @@ class SavedViewSerializer(OwnedObjectSerializer): ) ) except serializers.ValidationError as exc: - raise serializers.ValidationError({field_name: exc.detail}) + raise serializers.ValidationError({field_name: exc.detail}) from exc del normalized_data[field_name] ret = super().to_internal_value(normalized_data) @@ -1756,7 +1758,7 @@ class BulkEditSerializer( logger.exception(f"Error validating custom fields: {e}") raise serializers.ValidationError( f"{name} must be a list of integers or a dict of id:value pairs, see the log for details", - ) + ) from None elif not isinstance(custom_fields, list) or not all( isinstance(i, int) for i in ids ): @@ -1824,7 +1826,7 @@ class BulkEditSerializer( try: Tag.objects.get(id=tag_id) except Tag.DoesNotExist: - raise serializers.ValidationError("Tag does not exist") + raise serializers.ValidationError("Tag does not exist") from None else: raise serializers.ValidationError("tag not specified") @@ -1837,7 +1839,9 @@ class BulkEditSerializer( try: DocumentType.objects.get(id=document_type_id) except DocumentType.DoesNotExist: - raise serializers.ValidationError("Document type does not exist") + raise serializers.ValidationError( + "Document type does not exist", + ) from None else: raise serializers.ValidationError("document_type not specified") @@ -1849,7 +1853,9 @@ class BulkEditSerializer( try: Correspondent.objects.get(id=correspondent_id) except Correspondent.DoesNotExist: - raise serializers.ValidationError("Correspondent does not exist") + raise serializers.ValidationError( + "Correspondent does not exist", + ) from None else: raise serializers.ValidationError("correspondent not specified") @@ -1863,7 +1869,7 @@ class BulkEditSerializer( except StoragePath.DoesNotExist: raise serializers.ValidationError( "Storage path does not exist", - ) + ) from None else: raise serializers.ValidationError("storage path not specified") @@ -1918,7 +1924,7 @@ class BulkEditSerializer( ): raise serializers.ValidationError("invalid rotation degrees") except ValueError: - raise serializers.ValidationError("invalid rotation degrees") + raise serializers.ValidationError("invalid rotation degrees") from None def _validate_source_mode(self, parameters) -> None: source_mode = parameters.get( @@ -1948,7 +1954,7 @@ class BulkEditSerializer( pages.append([int(doc)]) parameters["pages"] = pages except ValueError: - raise serializers.ValidationError("invalid pages specified") + raise serializers.ValidationError("invalid pages specified") from None if "delete_originals" in parameters: if not isinstance(parameters["delete_originals"], bool): @@ -2218,14 +2224,14 @@ class PostDocumentSerializer(serializers.Serializer[dict[str, Any]]): raise serializers.ValidationError( _("Custom field id must be an integer: %(id)s") % {"id": field_id}, - ) + ) from None try: field = CustomField.objects.get(id=field_id_int) except CustomField.DoesNotExist: raise serializers.ValidationError( _("Custom field with id %(id)s does not exist") % {"id": field_id_int}, - ) + ) from None custom_field_serializer.validate( { "field": field, @@ -2242,7 +2248,7 @@ class PostDocumentSerializer(serializers.Serializer[dict[str, Any]]): _( "Custom fields must be a list of integers or an object mapping ids to values.", ), - ) + ) from None if CustomField.objects.filter(id__in=ids).count() != len(set(ids)): raise serializers.ValidationError( _("Some custom fields don't exist or were specified twice."), @@ -2353,7 +2359,9 @@ class EmailSerializer(DocumentListSerializer): for address in address_list: email_validator(address) except ValidationError: - raise serializers.ValidationError(f"Invalid email address: {address}") + raise serializers.ValidationError( + f"Invalid email address: {address}", + ) from None return ",".join(address_list) @@ -2777,7 +2785,7 @@ class ShareLinkBundleSerializer(OwnedObjectSerializer): return share_link_bundle def get_document_count(self, obj: ShareLinkBundle) -> int: - return getattr(obj, "document_total") or obj.documents.count() + return obj.document_total or obj.documents.count() class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin): @@ -3125,7 +3133,7 @@ class WorkflowActionSerializer(serializers.ModelSerializer[WorkflowAction]): except (ValueError, KeyError) as e: raise serializers.ValidationError( {"assign_title": f'Invalid f-string detected: "{e.args[0]}"'}, - ) + ) from None if ( "type" in attrs diff --git a/src/documents/tests/test_bulk_edit.py b/src/documents/tests/test_bulk_edit.py index 8d0c893eb..2421df4a7 100644 --- a/src/documents/tests/test_bulk_edit.py +++ b/src/documents/tests/test_bulk_edit.py @@ -764,7 +764,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): sig.set.return_value.apply_async.side_effect = Exception("boom") mock_consume_file.return_value = sig - with self.assertRaises(Exception): + with self.assertRaisesRegex(Exception, "boom"): bulk_edit.merge(doc_ids, delete_originals=True) self.doc1.refresh_from_db() @@ -1047,6 +1047,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): for call, expected_id in zip( mock_consume_delay.call_args_list, doc_ids, + strict=False, ): task_kwargs = call.kwargs["kwargs"] self.assertEqual(task_kwargs["input_doc"].root_document_id, expected_id) @@ -1305,7 +1306,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): sig.apply_async.side_effect = Exception("boom") mock_chord.return_value = sig - with self.assertRaises(Exception): + with self.assertRaisesRegex(Exception, "boom"): bulk_edit.edit_pdf(doc_ids, operations, delete_original=True) self.doc2.refresh_from_db() @@ -1417,7 +1418,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): {"page": 9999}, # invalid page, forces error during PDF load ] with self.assertLogs("paperless.bulk_edit", level="ERROR"): - with self.assertRaises(Exception): + with self.assertRaises(ValueError): bulk_edit.edit_pdf(doc_ids, operations) mock_group.assert_not_called() mock_consume_file.assert_not_called() diff --git a/src/documents/tests/test_classifier.py b/src/documents/tests/test_classifier.py index 133dc88fe..3d1ac6bbf 100644 --- a/src/documents/tests/test_classifier.py +++ b/src/documents/tests/test_classifier.py @@ -782,8 +782,8 @@ class TestClassifier(DirectoriesMixin, TestCase): load_classifier(raise_exception=True) Path(settings.MODEL_FILE).touch() - mock_load.side_effect = Exception() - with self.assertRaises(Exception): + mock_load.side_effect = RuntimeError() + with self.assertRaises(RuntimeError): load_classifier(raise_exception=True) diff --git a/src/documents/tests/test_views.py b/src/documents/tests/test_views.py index a67590b81..a430aeca8 100644 --- a/src/documents/tests/test_views.py +++ b/src/documents/tests/test_views.py @@ -243,7 +243,7 @@ class TestViews(DirectoriesMixin, TestCase): "change": {"users": [], "groups": []}, } else: - assert False, f"Unexpected tag found: {tag['name']}" + raise AssertionError(f"Unexpected tag found: {tag['name']}") def test_list_no_n_plus_1_queries(self) -> None: """ diff --git a/src/documents/tests/test_workflows.py b/src/documents/tests/test_workflows.py index 243f9609e..fcbf9882e 100644 --- a/src/documents/tests/test_workflows.py +++ b/src/documents/tests/test_workflows.py @@ -2760,7 +2760,14 @@ class TestWorkflows( doc = Document.objects.create( title="test", ) - self.assertRaises(Exception, document_matches_workflow, doc, w, 99) + self.assertRaisesRegex( + Exception, + "not yet supported", + document_matches_workflow, + doc, + w, + 99, + ) def test_removal_action_document_updated_workflow(self) -> None: """ diff --git a/src/documents/tests/utils.py b/src/documents/tests/utils.py index 7e08b65a8..c63098c33 100644 --- a/src/documents/tests/utils.py +++ b/src/documents/tests/utils.py @@ -129,11 +129,12 @@ def util_call_with_backoff( status_codes.append(cause_exec.response.status_code) warnings.warn( f"HTTP Exception for {cause_exec.request.url} - {cause_exec}", + stacklevel=2, ) else: - warnings.warn(f"Unexpected error: {e}") + warnings.warn(f"Unexpected error: {e}", stacklevel=2) except Exception as e: # pragma: no cover - warnings.warn(f"Unexpected error: {e}") + warnings.warn(f"Unexpected error: {e}", stacklevel=2) retry_count = retry_count + 1 diff --git a/src/documents/views.py b/src/documents/views.py index 71713284d..db1215fee 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -285,7 +285,7 @@ def _get_more_like_id(query_params: dict[str, Any], user: User | None) -> int: pk=more_like_doc_id, ) except (TypeError, ValueError, Document.DoesNotExist): - raise PermissionDenied(_("Invalid more_like_id")) + raise PermissionDenied(_("Invalid more_like_id")) from None if user and not has_perms_owner_aware( user, @@ -1101,7 +1101,7 @@ class DocumentViewSet( "root_document", ).get(pk=pk) except Document.DoesNotExist: - raise Http404 + raise Http404 from None root_doc = get_root_document(doc) if request.user is not None and not has_perms_owner_aware( @@ -1264,7 +1264,7 @@ class DocumentViewSet( "root_document", ).get(id=pk) except Document.DoesNotExist: - raise Http404 + raise Http404 from None root_doc = get_root_document( request_doc, @@ -1579,7 +1579,7 @@ class DocumentViewSet( disposition="inline", ) except FileNotFoundError: - raise Http404 + raise Http404 from None @action(methods=["get"], detail=True, filter_backends=[]) @method_decorator(cache_control(no_cache=True)) @@ -1604,14 +1604,14 @@ class DocumentViewSet( return FileResponse(handle, content_type="image/webp") except FileNotFoundError: - raise Http404 + raise Http404 from None @action(methods=["get"], detail=True) def download(self, request, pk=None): try: return self.file_response(pk, request, "attachment") except (FileNotFoundError, Document.DoesNotExist): - raise Http404 + raise Http404 from None @action( methods=["get", "post", "delete"], @@ -1636,7 +1636,7 @@ class DocumentViewSet( ): return HttpResponseForbidden("Insufficient permissions to view notes") except Document.DoesNotExist: - raise Http404 + raise Http404 from None serializer = self.get_serializer(doc) @@ -1707,7 +1707,7 @@ class DocumentViewSet( try: note_id_int = int(note_id) except ValueError: - raise ValidationError({"id": "A valid integer is required."}) + raise ValidationError({"id": "A valid integer is required."}) from None note = get_object_or_404(Note, id=note_id_int, document=doc) if settings.AUDIT_LOG_ENABLED: LogEntry.objects.log_create( @@ -1751,7 +1751,7 @@ class DocumentViewSet( "Insufficient permissions to add share link", ) except Document.DoesNotExist: - raise Http404 + raise Http404 from None if request.method == "GET": now = timezone.now() @@ -1779,7 +1779,7 @@ class DocumentViewSet( "Insufficient permissions", ) except Document.DoesNotExist: # pragma: no cover - raise Http404 + raise Http404 from None # documents entries = [ @@ -1929,7 +1929,7 @@ class DocumentViewSet( ): return HttpResponseForbidden("Insufficient permissions") except Document.DoesNotExist: - raise Http404 + raise Http404 from None try: doc_name, doc_data = serializer.validated_data.get("document") @@ -1980,7 +1980,7 @@ class DocumentViewSet( "root_document", ).get(pk=pk) except Document.DoesNotExist: - raise Http404 + raise Http404 from None return get_root_document(root_doc) def _get_version_doc_for_root(self, root_doc: Document, version_id) -> Document: @@ -1989,7 +1989,7 @@ class DocumentViewSet( pk=version_id, ) except Document.DoesNotExist: - raise Http404 + raise Http404 from None if ( version_doc.id != root_doc.id @@ -2544,7 +2544,7 @@ class LogViewSet(ViewSet): try: limit = int(limit_param) except (TypeError, ValueError): - raise ValidationError({"limit": "Must be a positive integer"}) + raise ValidationError({"limit": "Must be a positive integer"}) from None if limit < 1: raise ValidationError({"limit": "Must be a positive integer"}) else: diff --git a/src/paperless/settings/custom.py b/src/paperless/settings/custom.py index d2a853c61..62c7c8ebf 100644 --- a/src/paperless/settings/custom.py +++ b/src/paperless/settings/custom.py @@ -331,7 +331,7 @@ def parse_dateparser_languages(languages: str | None) -> list[str]: language_list = languages.split("+") if languages else [] # There is an unfixed issue in zh-Hant and zh-Hans locales in the dateparser lib. # See: https://github.com/scrapinghub/dateparser/issues/875 - for index, language in enumerate(language_list): + for _, language in enumerate(language_list): if language.startswith("zh-") and "zh" not in language_list: logger.warning( f"Chinese locale detected: {language}. dateparser might fail to parse" diff --git a/src/paperless/validators.py b/src/paperless/validators.py index 6e3c78f79..16adb8e17 100644 --- a/src/paperless/validators.py +++ b/src/paperless/validators.py @@ -193,7 +193,7 @@ def reject_dangerous_svg(file: UploadedFile) -> None: tree = etree.parse(file, parser) root = tree.getroot() except etree.XMLSyntaxError: - raise ValidationError("Invalid SVG file.") + raise ValidationError("Invalid SVG file.") from None for element in root.iter(): tag: str = etree.QName(element.tag).localname.lower() diff --git a/src/paperless_ai/indexing.py b/src/paperless_ai/indexing.py index 7ec1fdba3..34f4d37cd 100644 --- a/src/paperless_ai/indexing.py +++ b/src/paperless_ai/indexing.py @@ -313,7 +313,7 @@ def update_llm_index( continue # Delete from docstore, FAISS IndexFlatL2 are append-only - for node in doc_nodes: + for _ in doc_nodes: remove_document_docstore_nodes(document, index) nodes.extend(build_document_node(document, chunk_size=chunk_size)) diff --git a/src/paperless_ai/tests/test_ai_classifier.py b/src/paperless_ai/tests/test_ai_classifier.py index 97e18eb47..df14e29a8 100644 --- a/src/paperless_ai/tests/test_ai_classifier.py +++ b/src/paperless_ai/tests/test_ai_classifier.py @@ -155,7 +155,7 @@ def test_get_ai_document_classification_failure(mock_run_llm_query, mock_documen mock_run_llm_query.side_effect = Exception("LLM query failed") # assert raises an exception - with pytest.raises(Exception): + with pytest.raises(ValueError, match="Unsupported LLM backend"): get_ai_document_classification(mock_document) diff --git a/src/paperless_ai/tests/test_ai_indexing.py b/src/paperless_ai/tests/test_ai_indexing.py index 339d75ead..68d248543 100644 --- a/src/paperless_ai/tests/test_ai_indexing.py +++ b/src/paperless_ai/tests/test_ai_indexing.py @@ -226,7 +226,7 @@ def test_get_or_create_storage_context_raises_exception( temp_llm_index_dir, mock_embed_model, ) -> None: - with pytest.raises(Exception): + with pytest.raises(ValueError): indexing.get_or_create_storage_context(rebuild=False) @@ -273,7 +273,7 @@ def test_load_or_build_index_raises_exception_when_no_nodes( return_value=MagicMock(), ), ): - with pytest.raises(Exception): + with pytest.raises(Exception): # noqa: B017 indexing.load_or_build_index() diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py index 96802fa49..d447002be 100644 --- a/src/paperless_mail/tests/test_mail.py +++ b/src/paperless_mail/tests/test_mail.py @@ -349,9 +349,10 @@ class MailMocker(DirectoriesMixin, FileSystemAssertsMixin, TestCase): len(expected_call_args), ) - for (mock_args, mock_kwargs), expected_signatures in zip( + for (_, mock_kwargs), expected_signatures in zip( self._queue_consumption_tasks_mock.call_args_list, expected_call_args, + strict=False, ): consume_tasks = mock_kwargs["consume_tasks"] @@ -361,6 +362,7 @@ class MailMocker(DirectoriesMixin, FileSystemAssertsMixin, TestCase): for consume_task, expected_signature in zip( consume_tasks, expected_signatures, + strict=False, ): input_doc = consume_task.kwargs["input_doc"] overrides = consume_task.kwargs["overrides"] @@ -383,7 +385,7 @@ class MailMocker(DirectoriesMixin, FileSystemAssertsMixin, TestCase): """ Applies pending actions to mails by inspecting calls to the queue_consumption_tasks method. """ - for args, kwargs in self._queue_consumption_tasks_mock.call_args_list: + for _, kwargs in self._queue_consumption_tasks_mock.call_args_list: message = kwargs["message"] rule = kwargs["rule"] apply_mail_action([], rule.pk, message.uid, message.subject, message.date) diff --git a/src/paperless_mail/tests/test_preprocessor.py b/src/paperless_mail/tests/test_preprocessor.py index 33c9b3839..982ebfe40 100644 --- a/src/paperless_mail/tests/test_preprocessor.py +++ b/src/paperless_mail/tests/test_preprocessor.py @@ -184,7 +184,12 @@ class TestMailMessageGpgDecryptor(TestMail): EMAIL_GNUPG_HOME=empty_gpg_home, ): message_decryptor = MailMessageDecryptor() - self.assertRaises(Exception, message_decryptor.run, encrypted_message) + self.assertRaisesRegex( + Exception, + "Decryption failed", + message_decryptor.run, + encrypted_message, + ) finally: # Clean up the temporary GPG home used only by this test try: