Repository: apache/airflow
File: providers/imap/src/airflow/providers/imap/hooks/imap.py (Python)

Code before:
            self._handle_not_found_mode(not_found_mode)

        self._create_files(mail_attachments, local_output_directory)

    def _handle_not_found_mode(self, not_found_mode: str) -> None:
        if not_found_mode not in ("raise", "warn", "ignore"):
            self.log.error('Invalid "not_found_mode" %s', not_found_mode)
        elif not_found_mode == "raise":
            raise AirflowException("No mail attachments found!")
        elif not_found_mode == "warn":
            self.log.warning("No mail attachments found!")

    def _retrieve_mails_attachments_by_name(
        self,
        name: str,
        check_regex: bool,
        latest_only: bool,
        max_mails: int | None,
        mail_folder: str,
        mail_filter: str,
    ) -> list[tuple]:
        if not self.mail_client:
            raise RuntimeError("The 'mail_client' should be initialized before!")

        if max_mails is not None and max_mails <= 0:
            raise AirflowException("max_mails must be a positive integer")

        all_matching_attachments: list[tuple] = []

        self.mail_client.select(mail_folder)

        for mail_id in self._list_mail_ids_desc(mail_filter, max_mails=max_mails):
            response_mail_body = self._fetch_mail_body(mail_id)
            matching_attachments = self._check_mail_body(response_mail_body, name, check_regex, latest_only)

            if matching_attachments:
                all_matching_attachments.extend(matching_attachments)
                if latest_only:
                    break

        self.mail_client.close()

        return all_matching_attachments

    def _list_mail_ids_desc(self, mail_filter: str, max_mails: int | None = None) -> Iterable[str]:
        if not self.mail_client:
            raise RuntimeError("The 'mail_client' should be initialized before!")
        _, data = self.mail_client.search(None, mail_filter)
        mail_ids = data[0].split()
        mail_ids_desc = reversed(mail_ids)


Review comment (suggestion):
Following a recent decision (see [dev list thread](https://lists.apache.org/thread/5rv4tz0oc27bgr4khx0on0jz8fpxvh55)), the directive is not to use `AirflowException`, but instead use native Python/Provider-native exceptions. In this case, it should become something like:

```suggestion
            raise ValueError("max_mails must be a positive integer")
```

+ Fix related unit test

Code after:

    def _handle_not_found_mode(self, not_found_mode: str) -> None:
        if not_found_mode not in ("raise", "warn", "ignore"):
            self.log.error('Invalid "not_found_mode" %s', not_found_mode)
        elif not_found_mode == "raise":
            raise AirflowException("No mail attachments found!")
        elif not_found_mode == "warn":
            self.log.warning("No mail attachments found!")

    def _retrieve_mails_attachments_by_name(
        self,
        name: str,
        check_regex: bool,
        latest_only: bool,
        max_mails: int | None,
        mail_folder: str,
        mail_filter: str,
    ) -> list[tuple]:
        if not self.mail_client:
            raise RuntimeError("The 'mail_client' should be initialized before!")

        if max_mails is not None and max_mails <= 0:
            raise ValueError("max_mails must be a positive integer")

        all_matching_attachments: list[tuple] = []

        self.mail_client.select(mail_folder)

        for mail_id in self._list_mail_ids_desc(mail_filter, max_mails=max_mails):
            response_mail_body = self._fetch_mail_body(mail_id)
            matching_attachments = self._check_mail_body(response_mail_body, name, check_regex, latest_only)

            if matching_attachments:
                all_matching_attachments.extend(matching_attachments)
                if latest_only:
                    break

        self.mail_client.close()

        return all_matching_attachments

    def _list_mail_ids_desc(self, mail_filter: str, max_mails: int | None = None) -> Iterable[str]:
        if not self.mail_client:
            raise RuntimeError("The 'mail_client' should be initialized before!")
        _, data = self.mail_client.search(None, mail_filter)
        mail_ids = data[0].split()
        if max_mails is not None:
            mail_ids = mail_ids[-max_mails:]

        return reversed(mail_ids)