From 2e3ee25ec97467e162ed38750e0f780c11e915e3 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:34:35 -0500 Subject: [PATCH 1/5] Drop Python 3.9 support (#661) * Initial plan * Drop Python 3.9 support: update CI matrix, pyproject.toml, docs, and README Co-authored-by: seanthegeek <44679+seanthegeek@users.noreply.github.com> * Update Python 3.9 version table entry to note Debian 11/RHEL 9 usage Co-authored-by: seanthegeek <44679+seanthegeek@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: seanthegeek <44679+seanthegeek@users.noreply.github.com> --- .github/workflows/python-tests.yml | 2 +- README.md | 2 +- docs/source/index.md | 2 +- docs/source/installation.md | 4 ++-- parsedmarc/types.py | 2 +- pyproject.toml | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 97ffc07..7667da9 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v5 diff --git a/README.md b/README.md index 4f4921e..9cd7ca4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ for RHEL or Debian. | 3.6 | ❌ | Used in RHEL 8, but not supported by project dependencies | | 3.7 | ❌ | End of Life (EOL) | | 3.8 | ❌ | End of Life (EOL) | -| 3.9 | ✅ | Supported until August 2026 (Debian 11); May 2032 (RHEL 9) | +| 3.9 | ❌ | Used in Debian 11 and RHEL 9, but not supported by project dependencies | | 3.10 | ✅ | Actively maintained | | 3.11 | ✅ | Actively maintained; supported until June 2028 (Debian 12) | | 3.12 | ✅ | Actively maintained; supported until May 2035 (RHEL 10) | diff --git a/docs/source/index.md b/docs/source/index.md index a0c213e..c6e2c10 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -56,7 +56,7 @@ for RHEL or Debian. | 3.6 | ❌ | Used in RHEL 8, but not supported by project dependencies | | 3.7 | ❌ | End of Life (EOL) | | 3.8 | ❌ | End of Life (EOL) | -| 3.9 | ✅ | Supported until August 2026 (Debian 11); May 2032 (RHEL 9) | +| 3.9 | ❌ | Used in Debian 11 and RHEL 9, but not supported by project dependencies | | 3.10 | ✅ | Actively maintained | | 3.11 | ✅ | Actively maintained; supported until June 2028 (Debian 12) | | 3.12 | ✅ | Actively maintained; supported until May 2035 (RHEL 10) | diff --git a/docs/source/installation.md b/docs/source/installation.md index 6d06a65..5a8a519 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -162,10 +162,10 @@ sudo -u parsedmarc virtualenv /opt/parsedmarc/venv ``` CentOS/RHEL 8 systems use Python 3.6 by default, so on those systems -explicitly tell `virtualenv` to use `python3.9` instead +explicitly tell `virtualenv` to use `python3.10` instead ```bash -sudo -u parsedmarc virtualenv -p python3.9 /opt/parsedmarc/venv +sudo -u parsedmarc virtualenv -p python3.10 /opt/parsedmarc/venv ``` Activate the virtualenv diff --git a/parsedmarc/types.py b/parsedmarc/types.py index 54af485..f0d367d 100644 --- a/parsedmarc/types.py +++ b/parsedmarc/types.py @@ -2,7 +2,7 @@ from __future__ import annotations from typing import Any, Dict, List, Literal, Optional, TypedDict, Union -# NOTE: This module is intentionally Python 3.9 compatible. +# NOTE: This module is intentionally Python 3.10 compatible. # - No PEP 604 unions (A | B) # - No typing.NotRequired / Required (3.11+) to avoid an extra dependency. # For optional keys, use total=False TypedDicts. diff --git a/pyproject.toml b/pyproject.toml index fd1f797..b223f9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "hatchling>=1.27.0", ] -requires_python = ">=3.9,<3.14" +requires_python = ">=3.10,<3.14" build-backend = "hatchling.build" [project] @@ -29,7 +29,7 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 3" ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "azure-identity>=1.8.0", "azure-monitor-ingestion>=1.0.0", From 5aaaedf46348f34c263ad6c9f1a3e03a67800fc7 Mon Sep 17 00:00:00 2001 From: Majid Burney Date: Tue, 3 Mar 2026 08:35:05 -0800 Subject: [PATCH 2/5] Use correct key names for elasticsearch/opensearch api keys (#660) --- parsedmarc/cli.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parsedmarc/cli.py b/parsedmarc/cli.py index 012b8d2..0f3821f 100644 --- a/parsedmarc/cli.py +++ b/parsedmarc/cli.py @@ -1058,10 +1058,10 @@ def _main(): opts.elasticsearch_password = elasticsearch_config["password"] # Until 8.20 if "apiKey" in elasticsearch_config: - opts.elasticsearch_apiKey = elasticsearch_config["apiKey"] + opts.elasticsearch_api_key = elasticsearch_config["apiKey"] # Since 8.20 if "api_key" in elasticsearch_config: - opts.elasticsearch_apiKey = elasticsearch_config["api_key"] + opts.elasticsearch_api_key = elasticsearch_config["api_key"] if "opensearch" in config: opensearch_config = config["opensearch"] @@ -1098,10 +1098,10 @@ def _main(): opts.opensearch_password = opensearch_config["password"] # Until 8.20 if "apiKey" in opensearch_config: - opts.opensearch_apiKey = opensearch_config["apiKey"] + opts.opensearch_api_key = opensearch_config["apiKey"] # Since 8.20 if "api_key" in opensearch_config: - opts.opensearch_apiKey = opensearch_config["api_key"] + opts.opensearch_api_key = opensearch_config["api_key"] if "splunk_hec" in config.sections(): hec_config = config["splunk_hec"] From 3d8a99b5d3c909fdddfa0fa147a9517a931da13e Mon Sep 17 00:00:00 2001 From: Sean Whalen Date: Tue, 3 Mar 2026 11:43:44 -0500 Subject: [PATCH 3/5] 9.1.1 - Fix the use of Elasticsearch and OpenSearch API keys (PR #660 fixes issue #653) - Drop support for Python 3.9 (PR #661) --- CHANGELOG.md | 10 ++++++++++ parsedmarc/constants.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b8c45e..10b4a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 9.1.1 + +## Fixes + +- Fix the use of Elasticsearch and OpenSearch API keys (PR #660 fixes issue #653) + +## Changes + +- Drop support for Python 3.9 (PR #661) + ## 9.1.0 ## Enhancements diff --git a/parsedmarc/constants.py b/parsedmarc/constants.py index 1fff6eb..9a2e506 100644 --- a/parsedmarc/constants.py +++ b/parsedmarc/constants.py @@ -1,3 +1,3 @@ -__version__ = "9.1.0" +__version__ = "9.1.1" USER_AGENT = f"parsedmarc/{__version__}" From d987943c227d01470af357fbf1eb991cda8d9f1e Mon Sep 17 00:00:00 2001 From: Sean Whalen Date: Tue, 3 Mar 2026 11:46:13 -0500 Subject: [PATCH 4/5] Update changelog formatting for version 9.1.1 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10b4a7f..2ec208a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,11 @@ ## 9.1.1 -## Fixes +### Fixes - Fix the use of Elasticsearch and OpenSearch API keys (PR #660 fixes issue #653) -## Changes +### Changes - Drop support for Python 3.9 (PR #661) From 9551c8b467e1603018bcd6756a6036defd8a8379 Mon Sep 17 00:00:00 2001 From: Sean Whalen Date: Tue, 3 Mar 2026 21:00:55 -0500 Subject: [PATCH 5/5] Add AGENTS.md for AI agent guidance and link from CLAUDE.md --- AGENTS.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 3 +++ 2 files changed, 67 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..39b9560 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,64 @@ +# AGENTS.md + +This file provides guidance to AI agents when working with code in this repository. + +## Project Overview + +parsedmarc is a Python module and CLI utility for parsing DMARC aggregate (RUA), forensic (RUF), and SMTP TLS reports. It reads reports from IMAP, Microsoft Graph, Gmail API, Maildir, mbox files, or direct file paths, and outputs to JSON/CSV, Elasticsearch, OpenSearch, Splunk, Kafka, S3, Azure Log Analytics, syslog, or webhooks. + +## Common Commands + +```bash +# Install with dev/build dependencies +pip install .[build] + +# Run all tests with coverage +pytest --cov --cov-report=xml tests.py + +# Run a single test +pytest tests.py::Test::testAggregateSamples + +# Lint and format +ruff check . +ruff format . + +# Test CLI with sample reports +parsedmarc --debug -c ci.ini samples/aggregate/* +parsedmarc --debug -c ci.ini samples/forensic/* + +# Build docs +cd docs && make html + +# Build distribution +hatch build +``` + +To skip DNS lookups during testing, set `GITHUB_ACTIONS=true`. + +## Architecture + +**Data flow:** Input sources → CLI (`cli.py:_main`) → Parse (`__init__.py`) → Enrich (DNS/GeoIP via `utils.py`) → Output integrations + +### Key modules + +- `parsedmarc/__init__.py` — Core parsing logic. Main functions: `parse_report_file()`, `parse_report_email()`, `parse_aggregate_report_xml()`, `parse_forensic_report()`, `parse_smtp_tls_report_json()`, `get_dmarc_reports_from_mailbox()`, `watch_inbox()` +- `parsedmarc/cli.py` — CLI entry point (`_main`), config file parsing, output orchestration +- `parsedmarc/types.py` — TypedDict definitions for all report types (`AggregateReport`, `ForensicReport`, `SMTPTLSReport`, `ParsingResults`) +- `parsedmarc/utils.py` — IP/DNS/GeoIP enrichment, base64 decoding, compression handling +- `parsedmarc/mail/` — Polymorphic mail connections: `IMAPConnection`, `GmailConnection`, `MSGraphConnection`, `MaildirConnection` +- `parsedmarc/{elastic,opensearch,splunk,kafkaclient,loganalytics,syslog,s3,webhook,gelf}.py` — Output integrations + +### Report type system + +`ReportType = Literal["aggregate", "forensic", "smtp_tls"]`. Exception hierarchy: `ParserError` → `InvalidDMARCReport` → `InvalidAggregateReport`/`InvalidForensicReport`, and `InvalidSMTPTLSReport`. + +### Caching + +IP address info cached for 4 hours, seen aggregate report IDs cached for 1 hour (via `ExpiringDict`). + +## Code Style + +- Ruff for formatting and linting (configured in `.vscode/settings.json`) +- TypedDict for structured data, type hints throughout +- Python ≥3.10 required +- Tests are in a single `tests.py` file using unittest; sample reports live in `samples/` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..07926f1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# CLAUD.md + +@AGENTS.md