mirror of
https://github.com/domainaware/parsedmarc.git
synced 2026-08-01 21:22:18 +00:00
* Centralize configuration handling with ParserConfig (#503) Add parsedmarc/config.py with ParserConfig, a frozen dataclass carrying every parsing/enrichment option plus the three shared caches (IP address info, seen aggregate report IDs, reverse DNS map). All eight public parsing/mailbox functions accept a keyword-only config= argument; when provided, the individual option keyword arguments are ignored in favor of the config's values, and every existing per-option keyword argument keeps working unchanged. The three hand-copied parse_kwargs dicts and the dns_timeout<->timeout rename chain are gone; the CLI builds one ParserConfig per run (rebuilt on SIGHUP) and passes it everywhere. Explicitly constructed configs own fresh isolated caches; dataclasses.replace() shares them; pickling drops cache contents and rebinds the unpickling process's module defaults, preserving the per-worker cache behavior of n_procs parallel parsing. The module globals IP_ADDRESS_CACHE / SEEN_AGGREGATE_REPORT_IDS / REVERSE_DNS_MAP remain, identity-preserved, as re-exports of the default caches. Bug fixes that ride along, each with a regression test: - One-shot mailbox runs now honor [general] dns_timeout/dns_retries; the CLI call site never forwarded dns_timeout, so the library's stray 6.0 default silently applied. - Lazily-triggered reverse DNS map loads (get_ip_address_info / get_service_from_reverse_dns_base_domain, including in n_procs workers) now thread psl_overrides_path/psl_overrides_url through to load_reverse_dns_map instead of clobbering operator-configured PSL overrides with the bundled defaults. - get_dmarc_reports_from_mailbox() and watch_inbox() dns_timeout defaults unified to DEFAULT_DNS_TIMEOUT (2.0s) from a stray 6.0, and normalize_timespan_threshold_hours to the float 24.0 used everywhere else. Closes #503 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address CI and review feedback on #851 - Wrap the IMAPConnection example in usage.md so ruff format is clean over the docs code blocks (CI runs ruff format --check on the whole repo; the local runs were scoped to parsedmarc/ and tests/ and missed it). - Fix the pre-existing "URL ro a reverse DNS map" docstring typo in get_service_from_reverse_dns_base_domain, caught by Copilot on the adjacent hunk. - Import parsedmarc.config once, as an aliased plain import, in tests/test_config.py instead of mixing import and import-from of the same module (flagged by code quality scanning); the aliased module import also keeps pyright able to resolve the submodule attribute access. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address second Copilot review round on #851 - Add parse_aggregate_report_file() to the library entry-point list in usage.md; the following paragraph describes the config= contract for "each of these functions", so the list must name all eight config-accepting entry points. - ParserConfig.__setstate__ now initializes every non-cache field to its class default before applying the pickled state, so a config serialized by an older parsedmarc version (whose state predates fields added later) unpickles with the newer fields at their defaults instead of unset entirely (__init__ never runs during unpickling, so an absent field would raise AttributeError on first access). Covered by a regression test that feeds __setstate__ a partial state dict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4f4733003c
commit
4e80047e68
@@ -490,6 +490,67 @@ class TestLoadReverseDnsMapReloadsPSLOverrides(unittest.TestCase):
|
||||
offline=True,
|
||||
)
|
||||
|
||||
def _write_psl_overrides_file(self):
|
||||
"""Write a temp PSL overrides file with a unique suffix and register
|
||||
cleanup, returning its path."""
|
||||
tf = tempfile.NamedTemporaryFile(
|
||||
"w", suffix=".txt", delete=False, encoding="utf-8"
|
||||
)
|
||||
tf.write(".internal.example-503.net\n")
|
||||
tf.close()
|
||||
self.addCleanup(os.unlink, tf.name)
|
||||
return tf.name
|
||||
|
||||
def test_lazy_reverse_dns_map_load_applies_psl_overrides(self):
|
||||
"""Regression test for GitHub issue #503: the lazy
|
||||
``load_reverse_dns_map()`` call inside
|
||||
``get_service_from_reverse_dns_base_domain`` previously omitted
|
||||
``psl_overrides_path``/``psl_overrides_url``, so
|
||||
``load_reverse_dns_map``'s unconditional ``load_psl_overrides()``
|
||||
call (utils.py) silently reloaded ``psl_overrides`` with the bundled
|
||||
defaults, discarding an operator-configured overrides file. This
|
||||
proves the lazy load now threads the caller's
|
||||
``psl_overrides_path`` through: after calling with an empty
|
||||
``reverse_dns_map`` (which forces the lazy load) and a custom
|
||||
``psl_overrides_path``, the custom override must be in effect.
|
||||
"""
|
||||
path = self._write_psl_overrides_file()
|
||||
parsedmarc.utils.get_service_from_reverse_dns_base_domain(
|
||||
"something.example",
|
||||
reverse_dns_map={},
|
||||
always_use_local_file=True,
|
||||
offline=True,
|
||||
psl_overrides_path=path,
|
||||
)
|
||||
self.assertEqual(
|
||||
parsedmarc.utils.get_base_domain("deep.sub.internal.example-503.net"),
|
||||
"internal.example-503.net",
|
||||
)
|
||||
|
||||
def test_get_ip_address_info_lazy_load_applies_psl_overrides(self):
|
||||
"""Regression test for GitHub issue #503, exercising the
|
||||
ASN-fallback lazy ``load_reverse_dns_map()`` call inside
|
||||
``get_ip_address_info`` (used when no PTR record resolves). Before
|
||||
the fix, this lazy load also omitted ``psl_overrides_path``/
|
||||
``psl_overrides_url``, so it clobbered operator-configured PSL
|
||||
overrides with the bundled defaults the same way. ``offline=True``
|
||||
forces the no-PTR path so this call reaches the ASN-fallback lazy
|
||||
load rather than the PTR-driven
|
||||
``get_service_from_reverse_dns_base_domain`` call.
|
||||
"""
|
||||
path = self._write_psl_overrides_file()
|
||||
parsedmarc.utils.get_ip_address_info(
|
||||
"192.0.2.1",
|
||||
offline=True,
|
||||
reverse_dns_map={},
|
||||
always_use_local_files=True,
|
||||
psl_overrides_path=path,
|
||||
)
|
||||
self.assertEqual(
|
||||
parsedmarc.utils.get_base_domain("deep.sub.internal.example-503.net"),
|
||||
"internal.example-503.net",
|
||||
)
|
||||
|
||||
|
||||
class TestGetBaseDomainWithOverrides(unittest.TestCase):
|
||||
"""`get_base_domain` must honour the current psl_overrides list."""
|
||||
|
||||
Reference in New Issue
Block a user