Files
parsedmarc/pyproject.toml
T
Sean WhalenandClaude Sonnet 5.5 3311db4e16 Drop Python 3.10 and add Python 3.15 to the supported versions and CI (#953)
Python 3.10 reaches end of life in October 2026. requires-python is now
>=3.11, the unused requires_python key under [build-system] (not read by
hatchling, and its <3.15 bound read as excluding 3.15) is removed, and
Python 3.15 joins the CI matrix, classifiers, and support tables. Docs,
AGENTS.md, and comments that named 3.10 as the floor are updated.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-10 12:51:04 -05:00

254 lines
9.5 KiB
TOML

[build-system]
requires = [
"hatchling>=1.27.0",
]
build-backend = "hatchling.build"
[project]
name = "parsedmarc"
dynamic = [
"version",
]
description = "A Python package and CLI for parsing aggregate, failure, and SMTP TLS DMARC reports"
readme = "README.md"
license = "Apache-2.0"
authors = [
{ name = "Sean Whalen", email = "whalenster@gmail.com" },
]
keywords = [
"DMARC",
"parser",
"reporting",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
]
requires-python = ">=3.11"
# The base install is the parsing core plus a working core CLI: file,
# IMAP, Maildir, and mbox input; CSV/JSON, Splunk HEC, webhook, and
# syslog output. Every other output and mailbox integration lives in an extra
# below, so a library or small-mail-host install does not have to carry
# the Elasticsearch, OpenSearch, Kafka, AWS, Azure, Gmail, and Microsoft
# Graph SDKs (see issue #883).
dependencies = [
# The [doh] extra supplies the HTTP client DNS over HTTPS needs (httpx,
# httpcore and h2 on dnspython 2.7 and 2.8; httpx2, httpcore2 and h2 on
# 2.9+; utils.py builds its session from whichever client library
# dnspython imported). 2.7.0 is the floor verified against the
# dns.nameserver and dns.query.https(session=...) APIs utils.py builds
# on.
"dnspython[doh]>=2.7.0",
"expiringdict>=1.1.4",
# The runtime HTTP library (utils.py fetches, webhook and Splunk HEC
# clients, Graph error handling in cli.py). The floor matches
# microsoft-kiota-http's own requirement.
"httpx>=0.25",
"lxml>=4.4.0",
# No extras: the base mailsuite supplies IMAP and Maildir connections
# plus mail-parser. Gmail and Microsoft Graph come from the gmail and
# msgraph extras below.
"mailsuite>=2.3.5",
"maxminddb>=2.0.0",
"publicsuffixlist>=0.10.0",
# Imported directly at utils.py:39 (dateutil.parser). It used to
# arrive transitively via dateparser, which nothing ever imported.
"python-dateutil>=2.8.0",
"tqdm>=4.31.1",
"xmltodict>=0.12.0",
"PyYAML>=6.0.3"
]
# Splunk HEC, webhook, and syslog outputs deliberately have no extra:
# they need only httpx (a base dependency) and the standard library, so
# do not add empty marker extras for them.
[project.optional-dependencies]
elastic = [
"elasticsearch>=8.18,<9",
]
opensearch = [
"opensearch-py>=2.4.2,<=4.0.0",
# boto3 supplies the SigV4 signer opensearch.py imports for AWS auth.
"boto3>=1.16.63",
]
kafka = [
"kafka-python>=2.3.2",
]
s3 = [
"boto3>=1.16.63",
]
gelf = [
"pygelf>=0.4.2",
]
loganalytics = [
"azure-identity>=1.8.0",
"azure-monitor-ingestion>=1.0.0",
]
msgraph = [
"mailsuite[msgraph]>=2.3.5",
# Imported directly in cli.py for Graph error handling; otherwise
# only a transitive dep of mailsuite[msgraph] -> msgraph-sdk.
"microsoft-kiota-abstractions>=1.8.0",
]
gmail = [
"mailsuite[gmail]>=2.3.5",
]
# `all` must stay the union of every extra above — and must keep
# excluding `postgresql`: psycopg's prebuilt binary wheels do not exist
# for every platform/arch, so folding it in here would make
# `pip install parsedmarc[all]` fail on platforms it does not cover.
# Spelled out as an explicit package list rather than a self-referential
# `parsedmarc[elastic,...]` spec, which would make the project a
# dependency of itself; keep this list in sync when an extra above
# gains, drops, or re-floors a package.
all = [
"azure-identity>=1.8.0",
"azure-monitor-ingestion>=1.0.0",
"boto3>=1.16.63",
"elasticsearch>=8.18,<9",
"kafka-python>=2.3.2",
"mailsuite[gmail,msgraph]>=2.3.5",
"microsoft-kiota-abstractions>=1.8.0",
"opensearch-py>=2.4.2,<=4.0.0",
"pygelf>=0.4.2",
]
postgresql = [
# Optional output backend. psycopg ships prebuilt binary wheels via the
# [binary] extra, but those wheels don't exist for every platform/arch,
# so PostgreSQL support is opt-in rather than a mandatory dependency.
"psycopg[binary]>=3.1.0",
]
build = [
# Used only by maintainer tooling under parsedmarc/resources/maps/ —
# `collect_domain_info.py --use-search-fallback` falls back to a
# DuckDuckGo search when the homepage fetch returns a bot-block / parked
# / empty page. Optional import; the script runs without it as long as
# the fallback flag isn't passed.
"ddgs>=9.0.0",
"hatch>=1.14.0",
"myst-parser[linkify]",
"nose",
# Pinned exactly: pyright's checks evolve between releases, so an
# unpinned version could break CI without any code change. Bump
# deliberately (and fix any new findings) rather than implicitly.
"pyright==1.1.411",
"pytest",
"pytest-cov",
# Test-only: tests/test_init.py imports pytz for a fixed-offset
# timezone. It used to arrive transitively via dateparser, which the
# base dependencies no longer declare.
"pytz",
# Used only by the out-of-wheel maintainer script
# parsedmarc/resources/maps/collect_domain_info.py, which deliberately
# stays on requests because its permissive-TLS fallback is built on
# urllib3's HTTPAdapter machinery.
"requests>=2.22.0",
# Pinned exactly for the same reason as pyright: ruff's default rule
# set evolves between releases (e.g. 0.16.0 began flagging the
# str.format() style this codebase used until then), so an unpinned
# version breaks CI without any code change. Bump deliberately and fix
# any new findings in the same PR as the bump.
"ruff==0.16.0",
"sphinx",
"sphinx_rtd_theme",
]
[project.scripts]
parsedmarc = "parsedmarc.cli:_main"
[project.urls]
Homepage = "https://domainaware.github.io/parsedmarc"
[tool.hatch.version]
path = "parsedmarc/constants.py"
[tool.hatch.build.targets.sdist]
include = [
"/parsedmarc",
]
[tool.hatch.build]
exclude = [
"base_reverse_dns.csv",
"unknown_base_reverse_dns.csv",
"README.md",
"AGENTS.md",
"CLAUDE.md",
"*.bak",
# Maintenance tooling: any Python file under parsedmarc/resources/maps/
# whose name doesn't start with `_` (i.e. everything except __init__.py,
# which must keep shipping for `importlib.resources.files()` lookups).
"parsedmarc/resources/maps/[!_]*.py",
]
[tool.ruff.lint]
# The rule set is selected explicitly rather than floating on ruff's
# defaults: ruff 0.16.0 expanded the default selection from the
# long-standing E4/E7/E9/F to many more rule families (BLE, SIM, C4, DTZ,
# I, PL, S, ...), some of which conflict with deliberate house style —
# e.g. BLE001 flags the parser's intentional broad catches that keep one
# malformed report from crashing a batch. E4/E7/E9/F is the pre-0.16
# default set; adopting any of the new families is a deliberate
# per-family decision, made here with a comment, not an upgrade side
# effect.
#
# UP006/UP007/UP035/UP045 enforce modern type-hint syntax: with
# requires-python >=3.11, PEP 585 builtins (list[int]) and PEP 604 unions
# (X | Y, X | None) are available, so keep the deprecated typing.List /
# Union / Optional spellings out of the codebase.
select = [
"E4", # import rules from pycodestyle
"E7", # statement rules from pycodestyle
"E9", # runtime/syntax error rules from pycodestyle
"F", # pyflakes
"UP006", # non-pep585-annotation: List -> list, Dict -> dict
"UP007", # non-pep604-annotation-union: Union[X, Y] -> X | Y
"UP030", # format-literals: "{0}".format(x) -> "{}".format(x)
"UP032", # f-string: "{}".format(x) -> f"{x}"
"UP035", # deprecated-import: typing.List etc. / typing -> collections.abc
"UP045", # non-pep604-annotation-optional: Optional[X] -> X | None
]
[tool.pyright]
# The whole codebase passes pyright with zero errors and warnings; CI
# enforces this (see .github/workflows/python-tests.yml). Run locally with
# `pyright` from the repo root. Requires the [all] and [postgresql] extras
# to be installed so the optional integration imports (parsedmarc/cli.py's
# guarded output modules, the psycopg import in parsedmarc/postgres.py)
# resolve.
include = ["parsedmarc", "tests", "docs"]
typeCheckingMode = "standard"
[tool.pytest.ini_options]
# Default to the per-module test layout under tests/. New tests should go
# into tests/test_<module>.py to match the file they exercise; do not
# reintroduce a monolithic tests.py.
testpaths = ["tests"]
[tool.coverage.run]
# Coverage measures shipped code only. Master's reported ≈66.9% on
# Codecov was an artefact of the old monolithic tests.py having no
# [tool.coverage.run] block, which let coverage's default behaviour
# measure every file imported during the run — including the test file
# itself at ~99% "covered". That inflated the headline by ~8 percentage
# points without any actual testing signal. Restricting to the parsedmarc
# package gives a meaningful number that tracks how much of the shipped
# library the test suite actually exercises.
source = ["parsedmarc"]
# Maintainer-only batch scripts under parsedmarc/resources/maps/ ship
# out of the wheel (see the [tool.hatch.build] exclude block above) —
# omit them so the headline number reflects only installed library code.
omit = [
"*/parsedmarc/resources/maps/*.py",
]