Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ permissions:

jobs:
# ---------------------------------------------------------------------------
# Lint. rustfmt and mypy --strict are enforced (blocking); clippy and ruff
# stay advisory (continue-on-error) until their debt clears, and each runs
# Lint. rustfmt, mypy --strict, and ruff are enforced (blocking); clippy
# stays advisory (continue-on-error) until its debt clears, and each runs
# regardless of the others:
# - rustfmt -> blocking (source is rustfmt-clean).
# - clippy -> flip to blocking after the str->bytes fix (#19).
# - mypy -> blocking: `mypy --strict fast_mail_parser/` is clean (#42).
# - ruff -> advisory: `ruff check .` reports issues in the package
# stubs (UP006/UP007) and in test code (I001/E501/UP*). Fixing
# those is out of scope here; flip to blocking once clean (#44).
# - ruff -> blocking: `ruff check .` is clean (config in ruff.toml).
# ---------------------------------------------------------------------------
lint:
name: Lint
Expand Down Expand Up @@ -58,15 +56,12 @@ jobs:
python -m pip install --upgrade pip mypy
mypy --strict fast_mail_parser/

# Advisory: `ruff check .` currently reports issues in the package stubs
# (UP006/UP007) and in test code (I001/E501/UP015). Fixing those is out
# of scope; drop continue-on-error once they are resolved (#44).
# Blocking: `ruff check .` is clean (config in ruff.toml).
- name: ruff check
if: always()
run: |
python -m pip install ruff
ruff check .
continue-on-error: true

# ---------------------------------------------------------------------------
# Supply-chain audit. Blocking (no continue-on-error): the dependency stack is
Expand Down
25 changes: 14 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,28 +129,31 @@ make benchmark
The following checks are run in CI. Run them locally before opening a PR:

```bash
cargo fmt --all -- --check # Rust formatting (enforced / blocking in CI)
cargo fmt --all -- --check # Rust formatting (blocking in CI)
cargo clippy --all-targets -- -D warnings -W clippy::cast_possible_truncation
mypy fast_mail_parser/ # type-stub checking (advisory in CI)
ruff check . # Python linting (advisory)
mypy --strict fast_mail_parser/ # type-stub checking (blocking in CI)
ruff check --fix . # Python lint + autofix (blocking in CI; config in ruff.toml)
```

Run **`ruff check --fix .`** before opening a PR — it auto-fixes most findings
(import order, modern typing, etc.); fix any remainder by hand so `ruff check .`
is clean.

In CI:

- **`cargo fmt --check`** is **blocking** — the source is rustfmt-clean and must
stay that way.
- **`cargo clippy`** and **`mypy`** are currently **advisory**
(`continue-on-error`) while their remaining debt clears. Please keep new code
clean under both even though they do not yet fail the build.
- `ruff` is advisory; keep Python code clean under it.
- **`cargo fmt --check`**, **`mypy --strict`**, and **`ruff check`** are
**blocking** — keep the source clean under all three.
- **`cargo clippy`** is currently **advisory** (`continue-on-error`) while its
remaining debt clears; please keep new code clean under it even though it does
not yet fail the build.

## Continuous integration

The [`Test`](.github/workflows/test.yml) workflow gates every pull request. It
consists of:

- **Lint** — `cargo fmt --check` (blocking), plus `cargo clippy` and `mypy`
(advisory).
- **Lint** — `cargo fmt --check`, `mypy --strict`, and `ruff check` (all
blocking), plus `cargo clippy` (advisory).
- **cargo audit** — a **blocking** supply-chain audit of the Rust dependency
stack (PyO3 0.29) against the RustSec advisory database. A new advisory
against any dependency fails the build.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion fast_mail_parser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .fast_mail_parser import parse_email, ParseError, PyMail, PyAttachment
from .fast_mail_parser import ParseError, PyAttachment, PyMail, parse_email

__all__ = [
"parse_email",
Expand Down
12 changes: 5 additions & 7 deletions fast_mail_parser/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import typing as t

__all__ = [
"parse_email",
"PyMail",
Expand All @@ -18,11 +16,11 @@ class PyMail:
def __init__(
self,
subject: str,
text_plain: t.List[str],
text_html: t.List[str],
text_plain: list[str],
text_html: list[str],
date: str,
attachments: t.List[PyAttachment],
headers: t.Dict[str, str],
attachments: list[PyAttachment],
headers: dict[str, str],
) -> None:
self.subject = subject
self.text_plain = text_plain
Expand All @@ -36,7 +34,7 @@ class ParseError(Exception):
"""Error happened during parsing email."""


def parse_email(payload: t.Union[str, bytes]) -> PyMail:
def parse_email(payload: str | bytes) -> PyMail:
"""Parse raw content of email and return structured datatype.

A missing ``Subject`` or ``Date`` header yields the empty string ``""``
Expand Down
5 changes: 3 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import pytest
import sys
from typing import Callable

import pytest

from fast_mail_parser import PyMail, parse_email

sys.path.pop(0)
Expand All @@ -10,7 +11,7 @@
@pytest.fixture(scope='module')
def read_mail() -> Callable:
def wrap(path: str):
with open(path, 'r') as f:
with open(path) as f:
return f.read()

return wrap
Expand Down
2 changes: 1 addition & 1 deletion tests/generate_rfc_corpus.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"""

import os
from email.message import EmailMessage
from email import policy
from email.message import EmailMessage

OUT_DIR = os.path.join(os.path.dirname(__file__), "data", "rfc")

Expand Down
7 changes: 4 additions & 3 deletions tests/test_attachments.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import typing as t
from fast_mail_parser import PyMail


Expand All @@ -7,13 +6,15 @@ def test__attachments_are_available(attachment_mail: PyMail):


def test__base64_content_is_decoded(attachment_mail: PyMail):
attachment = list(filter(lambda a: a.mimetype == 'image/png', attachment_mail.attachments)).pop()
attachment = list(
filter(lambda a: a.mimetype == 'image/png', attachment_mail.attachments)
).pop()

assert attachment.content == b'PNG here'


def test__expected_attachments_are_present(large_mail: PyMail):
expected_attachment_names: t.Set[str] = {'Lorem Ipsum - All the facts.pdf', 'Kitty Dark.png'}
expected_attachment_names: set[str] = {'Lorem Ipsum - All the facts.pdf', 'Kitty Dark.png'}
attachments = [a for a in large_mail.attachments if a.filename in expected_attachment_names]

assert len(attachments) == 2
3 changes: 1 addition & 2 deletions tests/test_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
pass.
"""

import typing as t

import pytest

Expand All @@ -34,7 +33,7 @@
MALFORMED_MESSAGE = " unexpected continuation\r\n\r\nbody"


def _public_attrs(obj: object) -> t.Set[str]:
def _public_attrs(obj: object) -> set[str]:
return {name for name in dir(obj) if not name.startswith("_")}


Expand Down