From 862312526e545bf92b880599f9040e2841cba29b Mon Sep 17 00:00:00 2001 From: Sean Whalen Date: Thu, 25 Jun 2026 15:26:04 -0400 Subject: [PATCH] Update docs --- _modules/parsedmarc.html | 173 +++++++++++++--------------- _modules/parsedmarc/elastic.html | 62 +++++----- _modules/parsedmarc/opensearch.html | 64 +++++----- _modules/parsedmarc/splunk.html | 12 +- _modules/parsedmarc/types.html | 135 +++++++++++----------- _modules/parsedmarc/utils.html | 104 ++++++++--------- 6 files changed, 271 insertions(+), 279 deletions(-) diff --git a/_modules/parsedmarc.html b/_modules/parsedmarc.html index bc058ed..3bc62be 100644 --- a/_modules/parsedmarc.html +++ b/_modules/parsedmarc.html @@ -103,15 +103,10 @@ from csv import DictWriter from datetime import date, datetime, timedelta, timezone, tzinfo from io import BytesIO, StringIO +from collections.abc import Callable, Sequence from typing import ( Any, BinaryIO, - Callable, - Dict, - List, - Optional, - Sequence, - Union, cast, ) @@ -262,7 +257,7 @@ return " (raised at {0}:{1})".format(last.filename, last.lineno) -def _text(value: Any) -> Optional[str]: +def _text(value: Any) -> str | None: """Unwrap a possibly-langAttrString value parsed by xmltodict. RFC 9990 changed several aggregate-report elements (extra_contact_info, @@ -286,7 +281,7 @@ begin: datetime, end: datetime, total_count: int, -) -> List[Dict[str, Any]]: +) -> list[dict[str, Any]]: """ Split the interval [begin, end) into daily buckets and distribute `total_count` proportionally across those buckets. @@ -348,7 +343,7 @@ if day_cursor > begin: day_cursor -= timedelta(days=1) - day_buckets: List[Dict[str, Any]] = [] + day_buckets: list[dict[str, Any]] = [] while day_cursor < end: day_start = day_cursor @@ -380,12 +375,12 @@ # Then apply a "largest remainder" rounding strategy to ensure the sum # equals exactly total_count. - exact_values: List[float] = [ + exact_values: list[float] = [ (b["seconds"] / interval_seconds) * total_count for b in day_buckets ] - floor_values: List[int] = [int(x) for x in exact_values] - fractional_parts: List[float] = [x - int(x) for x in exact_values] + floor_values: list[int] = [int(x) for x in exact_values] + fractional_parts: list[float] = [x - int(x) for x in exact_values] # How many counts do we still need to distribute after flooring? remainder = total_count - sum(floor_values) @@ -405,7 +400,7 @@ final_counts[idx] += 1 # --- Step 3: Build the final per-day result list ------------------------- - results: List[Dict[str, Any]] = [] + results: list[dict[str, Any]] = [] for bucket, count in zip(day_buckets, final_counts): if count > 0: results.append( @@ -466,12 +461,12 @@ def _parse_report_record( record: dict[str, Any], *, - ip_db_path: Optional[str] = None, + ip_db_path: str | None = None, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, offline: bool = False, - nameservers: Optional[list[str]] = None, + nameservers: list[str] | None = None, dns_timeout: float = DEFAULT_DNS_TIMEOUT, dns_retries: int = DEFAULT_DNS_MAX_RETRIES, is_rfc_9990: bool = False, @@ -730,7 +725,7 @@
[docs] -def parse_smtp_tls_report_json(report: Union[str, bytes]) -> SMTPTLSReport: +def parse_smtp_tls_report_json(report: str | bytes) -> SMTPTLSReport: """Parses and validates an SMTP TLS report""" required_fields = [ "organization-name", @@ -776,7 +771,7 @@
[docs] def parsed_smtp_tls_reports_to_csv_rows( - reports: Union[SMTPTLSReport, list[SMTPTLSReport]], + reports: SMTPTLSReport | list[SMTPTLSReport], ) -> list[dict[str, Any]]: """Converts one oor more parsed SMTP TLS reports into a list of single layer dict objects suitable for use in a CSV""" @@ -819,7 +814,7 @@
[docs] def parsed_smtp_tls_reports_to_csv( - reports: Union[SMTPTLSReport, list[SMTPTLSReport]], + reports: SMTPTLSReport | list[SMTPTLSReport], ) -> str: """ Converts one or more parsed SMTP TLS reports to flat CSV format, including @@ -871,15 +866,15 @@ def parse_aggregate_report_xml( xml: str, *, - ip_db_path: Optional[str] = None, + ip_db_path: str | None = None, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, offline: bool = False, - nameservers: Optional[list[str]] = None, + nameservers: list[str] | None = None, timeout: float = DEFAULT_DNS_TIMEOUT, retries: int = DEFAULT_DNS_MAX_RETRIES, - keep_alive: Optional[Callable] = None, + keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24.0, ) -> AggregateReport: """Parses a DMARC XML report string and returns a consistent dict @@ -916,7 +911,7 @@ # The final `is_rfc_9990` decision is made post-parse so that # RFC 9990-only fields (np, testing, discovery_method, generator, # human_result) can also vote it in. - xml_namespace: Optional[str] = None + xml_namespace: str | None = None namespace_match = xml_namespace_regex.search(xml) if namespace_match: xml_namespace = namespace_match.group(1) @@ -1172,7 +1167,7 @@
[docs] -def extract_report(content: Union[bytes, str, BinaryIO]) -> str: +def extract_report(content: bytes | str | BinaryIO) -> str: """ Extracts text from a zip or gzip file, as a base64-encoded string, file-like object, or bytes. @@ -1185,7 +1180,7 @@ str: The extracted text """ - file_object: Optional[BinaryIO] = None + file_object: BinaryIO | None = None header: bytes try: if isinstance(content, str): @@ -1265,7 +1260,7 @@
[docs] def extract_report_from_file_path( - file_path: Union[str, bytes, os.PathLike[str], os.PathLike[bytes]], + file_path: str | bytes | os.PathLike[str] | os.PathLike[bytes], ) -> str: """Extracts report from a file at the given file_path""" try: @@ -1279,17 +1274,17 @@
[docs] def parse_aggregate_report_file( - _input: Union[str, bytes, BinaryIO], + _input: str | bytes | BinaryIO, *, offline: bool = False, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, - ip_db_path: Optional[str] = None, - nameservers: Optional[list[str]] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, + ip_db_path: str | None = None, + nameservers: list[str] | None = None, dns_timeout: float = DEFAULT_DNS_TIMEOUT, dns_retries: int = DEFAULT_DNS_MAX_RETRIES, - keep_alive: Optional[Callable] = None, + keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24.0, ) -> AggregateReport: """Parses a file at the given path, a file-like object. or bytes as an @@ -1338,7 +1333,7 @@
[docs] def parsed_aggregate_reports_to_csv_rows( - reports: Union[AggregateReport, list[AggregateReport]], + reports: AggregateReport | list[AggregateReport], ) -> list[dict[str, Any]]: """ Converts one or more parsed aggregate reports to list of dicts in flat CSV @@ -1476,7 +1471,7 @@
[docs] def parsed_aggregate_reports_to_csv( - reports: Union[AggregateReport, list[AggregateReport]], + reports: AggregateReport | list[AggregateReport], ) -> str: """ Converts one or more parsed aggregate reports to flat CSV format, including @@ -1558,11 +1553,11 @@ msg_date: datetime, *, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, offline: bool = False, - ip_db_path: Optional[str] = None, - nameservers: Optional[list[str]] = None, + ip_db_path: str | None = None, + nameservers: list[str] | None = None, dns_timeout: float = DEFAULT_DNS_TIMEOUT, dns_retries: int = DEFAULT_DNS_MAX_RETRIES, strip_attachment_payloads: bool = False, @@ -1721,7 +1716,7 @@
[docs] def parsed_failure_reports_to_csv_rows( - reports: Union[FailureReport, list[FailureReport]], + reports: FailureReport | list[FailureReport], ) -> list[dict[str, Any]]: """ Converts one or more parsed failure reports to a list of dicts in flat CSV @@ -1765,7 +1760,7 @@
[docs] def parsed_failure_reports_to_csv( - reports: Union[FailureReport, list[FailureReport]], + reports: FailureReport | list[FailureReport], ) -> str: """ Converts one or more parsed failure reports to flat CSV format, including @@ -1825,18 +1820,18 @@
[docs] def parse_report_email( - input_: Union[bytes, str], + input_: bytes | str, *, offline: bool = False, - ip_db_path: Optional[str] = None, + ip_db_path: str | None = None, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, - nameservers: Optional[list[str]] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, + nameservers: list[str] | None = None, dns_timeout: float = DEFAULT_DNS_TIMEOUT, dns_retries: int = DEFAULT_DNS_MAX_RETRIES, strip_attachment_payloads: bool = False, - keep_alive: Optional[Callable] = None, + keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24.0, ) -> ParsedReport: """ @@ -1863,11 +1858,11 @@ * ``report_type``: ``aggregate`` or ``failure`` * ``report``: The parsed report """ - result: Optional[ParsedReport] = None + result: ParsedReport | None = None msg_date: datetime = datetime.now(timezone.utc) try: - input_data: Union[str, bytes, bytearray, memoryview] = input_ + input_data: str | bytes | bytearray | memoryview = input_ if isinstance(input_data, (bytes, bytearray, memoryview)): input_bytes = bytes(input_data) if is_outlook_msg(input_bytes): @@ -2054,7 +2049,7 @@ def _describe_parse_failure( - content: Union[str, bytes], + content: str | bytes, aggregate_error: InvalidAggregateReport, smtp_tls_error: InvalidSMTPTLSReport, email_error: InvalidDMARCReport, @@ -2089,18 +2084,18 @@
[docs] def parse_report_file( - input_: Union[bytes, str, os.PathLike[str], os.PathLike[bytes], BinaryIO], + input_: bytes | str | os.PathLike[str] | os.PathLike[bytes] | BinaryIO, *, - nameservers: Optional[list[str]] = None, + nameservers: list[str] | None = None, dns_timeout: float = DEFAULT_DNS_TIMEOUT, dns_retries: int = DEFAULT_DNS_MAX_RETRIES, strip_attachment_payloads: bool = False, - ip_db_path: Optional[str] = None, + ip_db_path: str | None = None, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, offline: bool = False, - keep_alive: Optional[Callable] = None, + keep_alive: Callable | None = None, normalize_timespan_threshold_hours: float = 24, ) -> ParsedReport: """Parses a DMARC aggregate or failure file at the given path, a @@ -2141,7 +2136,7 @@ if content.startswith(MAGIC_ZIP) or content.startswith(MAGIC_GZIP): content = extract_report(content) - results: Optional[ParsedReport] = None + results: ParsedReport | None = None # parse_report_file tries the three report formats in turn. When all three # reject the input, keep each format's specific error so the final message @@ -2200,14 +2195,14 @@ def get_dmarc_reports_from_mbox( input_: str, *, - nameservers: Optional[list[str]] = None, + nameservers: list[str] | None = None, dns_timeout: float = DEFAULT_DNS_TIMEOUT, dns_retries: int = DEFAULT_DNS_MAX_RETRIES, strip_attachment_payloads: bool = False, - ip_db_path: Optional[str] = None, + ip_db_path: str | None = None, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, offline: bool = False, normalize_timespan_threshold_hours: float = 24.0, ) -> ParsingResults: @@ -2346,18 +2341,18 @@ archive_folder: str = "Archive", delete: bool = False, test: bool = False, - ip_db_path: Optional[str] = None, + ip_db_path: str | None = None, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, offline: bool = False, - nameservers: Optional[list[str]] = None, + nameservers: list[str] | None = None, dns_timeout: float = 6.0, dns_retries: int = DEFAULT_DNS_MAX_RETRIES, strip_attachment_payloads: bool = False, - results: Optional[ParsingResults] = None, + results: ParsingResults | None = None, batch_size: int = 10, - since: Optional[Union[datetime, date, str]] = None, + since: datetime | date | str | None = None, create_folders: bool = True, normalize_timespan_threshold_hours: float = 24, ) -> ParsingResults: @@ -2400,7 +2395,7 @@ raise ValueError("Must supply a connection") # current_time useful to fetch_messages later in the program - current_time: Optional[Union[datetime, date, str]] = None + current_time: datetime | date | str | None = None aggregate_reports: list[AggregateReport] = [] failure_reports: list[FailureReport] = [] @@ -2487,7 +2482,7 @@ i + 1, message_limit, msg_uid ) ) - message_id: Union[int, str] + message_id: int | str if isinstance(connection, IMAPConnection): message_id = int(msg_uid) msg_content = connection.fetch_message(message_id) @@ -2692,19 +2687,19 @@ delete: bool = False, test: bool = False, check_timeout: int = 30, - ip_db_path: Optional[str] = None, + ip_db_path: str | None = None, always_use_local_files: bool = False, - reverse_dns_map_path: Optional[str] = None, - reverse_dns_map_url: Optional[str] = None, + reverse_dns_map_path: str | None = None, + reverse_dns_map_url: str | None = None, offline: bool = False, - nameservers: Optional[list[str]] = None, + nameservers: list[str] | None = None, dns_timeout: float = 6.0, dns_retries: int = DEFAULT_DNS_MAX_RETRIES, strip_attachment_payloads: bool = False, batch_size: int = 10, - since: Optional[Union[datetime, date, str]] = None, + since: datetime | date | str | None = None, normalize_timespan_threshold_hours: float = 24, - config_reloading: Optional[Callable] = None, + config_reloading: Callable | None = None, ): """ Watches the mailbox for new messages and @@ -2778,11 +2773,9 @@ [docs] def append_json( filename: str, - reports: Union[ - Sequence[AggregateReport], - Sequence[FailureReport], - Sequence[SMTPTLSReport], - ], + reports: Sequence[AggregateReport] + | Sequence[FailureReport] + | Sequence[SMTPTLSReport], ) -> None: """Append ``reports`` to a JSON array on disk, creating the file if needed. @@ -2978,18 +2971,18 @@ results: ParsingResults, host: str, mail_from: str, - mail_to: Optional[list[str]], + mail_to: list[str] | None, *, - mail_cc: Optional[list[str]] = None, - mail_bcc: Optional[list[str]] = None, + mail_cc: list[str] | None = None, + mail_bcc: list[str] | None = None, port: int = 0, require_encryption: bool = False, verify: bool = True, - username: Optional[str] = None, - password: Optional[str] = None, - subject: Optional[str] = None, - attachment_filename: Optional[str] = None, - message: Optional[str] = None, + username: str | None = None, + password: str | None = None, + subject: str | None = None, + attachment_filename: str | None = None, + message: str | None = None, ): """ Emails parsing results as a zip file diff --git a/_modules/parsedmarc/elastic.html b/_modules/parsedmarc/elastic.html index 878fc13..e1e20ed 100644 --- a/_modules/parsedmarc/elastic.html +++ b/_modules/parsedmarc/elastic.html @@ -84,7 +84,7 @@ from __future__ import annotations -from typing import Any, Optional, Union +from typing import Any from elasticsearch.helpers import reindex from elasticsearch_dsl import ( @@ -210,7 +210,7 @@ domain: str, selector: str, result: _DKIMResult, - human_result: Optional[str] = None, + human_result: str | None = None, ): self.dkim_results.append( _DKIMResult( @@ -226,7 +226,7 @@ domain: str, scope: str, result: _SPFResult, - human_result: Optional[str] = None, + human_result: str | None = None, ): self.spf_results.append( _SPFResult( @@ -339,15 +339,15 @@ def add_failure_details( self, - result_type: Optional[str] = None, - ip_address: Optional[str] = None, - receiving_ip: Optional[str] = None, - receiving_mx_helo: Optional[str] = None, - failed_session_count: Optional[int] = None, - sending_mta_ip: Optional[str] = None, - receiving_mx_hostname: Optional[str] = None, - additional_information_uri: Optional[str] = None, - failure_reason_code: Union[str, int, None] = None, + result_type: str | None = None, + ip_address: str | None = None, + receiving_ip: str | None = None, + receiving_mx_helo: str | None = None, + failed_session_count: int | None = None, + sending_mta_ip: str | None = None, + receiving_mx_hostname: str | None = None, + additional_information_uri: str | None = None, + failure_reason_code: str | int | None = None, ): _details = _SMTPTLSFailureDetailsDoc( result_type=result_type, @@ -382,9 +382,9 @@ successful_session_count: int, failed_session_count: int, *, - policy_string: Optional[str] = None, - mx_host_patterns: Optional[list[str]] = None, - failure_details: Optional[str] = None, + policy_string: str | None = None, + mx_host_patterns: list[str] | None = None, + failure_details: str | None = None, ): self.policies.append( policy_type=policy_type, @@ -407,14 +407,14 @@
[docs] def set_hosts( - hosts: Union[str, list[str]], + hosts: str | list[str], *, use_ssl: bool = False, - ssl_cert_path: Optional[str] = None, + ssl_cert_path: str | None = None, skip_certificate_verification: bool = False, - username: Optional[str] = None, - password: Optional[str] = None, - api_key: Optional[str] = None, + username: str | None = None, + password: str | None = None, + api_key: str | None = None, timeout: float = 60.0, serverless: bool = False, ): @@ -459,7 +459,7 @@
[docs] -def create_indexes(names: list[str], settings: Optional[dict[str, Any]] = None): +def create_indexes(names: list[str], settings: dict[str, Any] | None = None): """ Create Elasticsearch indexes @@ -496,8 +496,8 @@
[docs] def migrate_indexes( - aggregate_indexes: Optional[list[str]] = None, - failure_indexes: Optional[list[str]] = None, + aggregate_indexes: list[str] | None = None, + failure_indexes: list[str] | None = None, ): """ Updates index mappings @@ -549,9 +549,9 @@ [docs] def save_aggregate_report_to_elasticsearch( aggregate_report: dict[str, Any], - index_suffix: Optional[str] = None, - index_prefix: Optional[str] = None, - monthly_indexes: Optional[bool] = False, + index_suffix: str | None = None, + index_prefix: str | None = None, + monthly_indexes: bool | None = False, number_of_shards: int = 1, number_of_replicas: int = 0, ): @@ -728,9 +728,9 @@ [docs] def save_failure_report_to_elasticsearch( failure_report: dict[str, Any], - index_suffix: Optional[Any] = None, - index_prefix: Optional[str] = None, - monthly_indexes: Optional[bool] = False, + index_suffix: Any | None = None, + index_prefix: str | None = None, + monthly_indexes: bool | None = False, number_of_shards: int = 1, number_of_replicas: int = 0, ): @@ -913,8 +913,8 @@ [docs] def save_smtp_tls_report_to_elasticsearch( report: dict[str, Any], - index_suffix: Optional[str] = None, - index_prefix: Optional[str] = None, + index_suffix: str | None = None, + index_prefix: str | None = None, monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0, diff --git a/_modules/parsedmarc/opensearch.html b/_modules/parsedmarc/opensearch.html index 8bb458a..3c2992c 100644 --- a/_modules/parsedmarc/opensearch.html +++ b/_modules/parsedmarc/opensearch.html @@ -84,7 +84,7 @@ from __future__ import annotations -from typing import Any, Optional, Union +from typing import Any import boto3 from opensearchpy import ( @@ -201,7 +201,7 @@ domain: str, selector: str, result: _DKIMResult, - human_result: Optional[str] = None, + human_result: str | None = None, ): self.dkim_results.append( _DKIMResult( @@ -217,7 +217,7 @@ domain: str, scope: str, result: _SPFResult, - human_result: Optional[str] = None, + human_result: str | None = None, ): self.spf_results.append( _SPFResult( @@ -330,15 +330,15 @@ def add_failure_details( self, - result_type: Optional[str] = None, - ip_address: Optional[str] = None, - receiving_ip: Optional[str] = None, - receiving_mx_helo: Optional[str] = None, - failed_session_count: Optional[int] = None, - sending_mta_ip: Optional[str] = None, - receiving_mx_hostname: Optional[str] = None, - additional_information_uri: Optional[str] = None, - failure_reason_code: Union[str, int, None] = None, + result_type: str | None = None, + ip_address: str | None = None, + receiving_ip: str | None = None, + receiving_mx_helo: str | None = None, + failed_session_count: int | None = None, + sending_mta_ip: str | None = None, + receiving_mx_hostname: str | None = None, + additional_information_uri: str | None = None, + failure_reason_code: str | int | None = None, ): _details = _SMTPTLSFailureDetailsDoc( result_type=result_type, @@ -373,9 +373,9 @@ successful_session_count: int, failed_session_count: int, *, - policy_string: Optional[str] = None, - mx_host_patterns: Optional[list[str]] = None, - failure_details: Optional[str] = None, + policy_string: str | None = None, + mx_host_patterns: list[str] | None = None, + failure_details: str | None = None, ): self.policies.append( policy_type=policy_type, @@ -398,17 +398,17 @@
[docs] def set_hosts( - hosts: Union[str, list[str]], + hosts: str | list[str], *, - use_ssl: Optional[bool] = False, - ssl_cert_path: Optional[str] = None, + use_ssl: bool | None = False, + ssl_cert_path: str | None = None, skip_certificate_verification: bool = False, - username: Optional[str] = None, - password: Optional[str] = None, - api_key: Optional[str] = None, - timeout: Optional[float] = 60.0, + username: str | None = None, + password: str | None = None, + api_key: str | None = None, + timeout: float | None = 60.0, auth_type: str = "basic", - aws_region: Optional[str] = None, + aws_region: str | None = None, aws_service: str = "es", ): """ @@ -469,7 +469,7 @@
[docs] -def create_indexes(names: list[str], settings: Optional[dict[str, Any]] = None): +def create_indexes(names: list[str], settings: dict[str, Any] | None = None): """ Create OpenSearch indexes @@ -496,8 +496,8 @@
[docs] def migrate_indexes( - aggregate_indexes: Optional[list[str]] = None, - failure_indexes: Optional[list[str]] = None, + aggregate_indexes: list[str] | None = None, + failure_indexes: list[str] | None = None, ): """ Updates index mappings @@ -549,8 +549,8 @@ [docs] def save_aggregate_report_to_opensearch( aggregate_report: dict[str, Any], - index_suffix: Optional[str] = None, - index_prefix: Optional[str] = None, + index_suffix: str | None = None, + index_prefix: str | None = None, monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0, @@ -728,8 +728,8 @@ [docs] def save_failure_report_to_opensearch( failure_report: dict[str, Any], - index_suffix: Optional[str] = None, - index_prefix: Optional[str] = None, + index_suffix: str | None = None, + index_prefix: str | None = None, monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0, @@ -911,8 +911,8 @@ [docs] def save_smtp_tls_report_to_opensearch( report: dict[str, Any], - index_suffix: Optional[str] = None, - index_prefix: Optional[str] = None, + index_suffix: str | None = None, + index_prefix: str | None = None, monthly_indexes: bool = False, number_of_shards: int = 1, number_of_replicas: int = 0, diff --git a/_modules/parsedmarc/splunk.html b/_modules/parsedmarc/splunk.html index 24d7d3c..9dc187f 100644 --- a/_modules/parsedmarc/splunk.html +++ b/_modules/parsedmarc/splunk.html @@ -86,7 +86,7 @@ import json import socket -from typing import Any, Union +from typing import Any from urllib.parse import urlparse import requests @@ -146,7 +146,7 @@ self.session = requests.Session() self.timeout = timeout self.verify = verify - self._common_data: dict[str, Union[str, int, float, dict]] = dict( + self._common_data: dict[str, str | int | float | dict] = dict( host=self.host, source=self.source, index=self.index ) @@ -161,7 +161,7 @@ [docs] def save_aggregate_reports_to_splunk( self, - aggregate_reports: Union[list[dict[str, Any]], dict[str, Any]], + aggregate_reports: list[dict[str, Any]] | dict[str, Any], ): """ Saves aggregate DMARC reports to Splunk @@ -182,7 +182,7 @@ json_str = "" for report in aggregate_reports: for record in report["records"]: - new_report: dict[str, Union[str, int, float, dict]] = dict() + new_report: dict[str, str | int | float | dict] = dict() for metadata in report["report_metadata"]: new_report[metadata] = report["report_metadata"][metadata] new_report["interval_begin"] = record["interval_begin"] @@ -235,7 +235,7 @@ [docs] def save_failure_reports_to_splunk( self, - failure_reports: Union[list[dict[str, Any]], dict[str, Any]], + failure_reports: list[dict[str, Any]] | dict[str, Any], ): """ Saves failure DMARC reports to Splunk @@ -276,7 +276,7 @@
[docs] def save_smtp_tls_reports_to_splunk( - self, reports: Union[list[dict[str, Any]], dict[str, Any]] + self, reports: list[dict[str, Any]] | dict[str, Any] ): """ Saves aggregate DMARC reports to Splunk diff --git a/_modules/parsedmarc/types.html b/_modules/parsedmarc/types.html index a320fa8..46b8c76 100644 --- a/_modules/parsedmarc/types.html +++ b/_modules/parsedmarc/types.html @@ -82,12 +82,13 @@

Source code for parsedmarc.types

 from __future__ import annotations
 
-from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
+from typing import Any, Literal, TypedDict
 
-# 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.
+# NOTE: This module targets Python 3.10.
+# - PEP 604 unions (A | B) and PEP 585 generics (list[str]) are used; both are
+#   available in 3.10.
+# - No typing.NotRequired / Required (3.11+); for optional TypedDict keys, use
+#   total=False TypedDicts.
 
 
 ReportType = Literal["aggregate", "failure", "smtp_tls"]
@@ -98,14 +99,14 @@
 class AggregateReportMetadata(TypedDict):
     org_name: str
     org_email: str
-    org_extra_contact_info: Optional[str]
+    org_extra_contact_info: str | None
     report_id: str
     begin_date: str
     end_date: str
     timespan_requires_normalization: bool
     original_timespan_seconds: int
-    errors: List[str]
-    generator: Optional[str]
+ errors: list[str] + generator: str | None
@@ -117,11 +118,11 @@ aspf: str p: str sp: str - pct: Optional[str] - fo: Optional[str] - np: Optional[str] - testing: Optional[str] - discovery_method: Optional[str]
+ pct: str | None + fo: str | None + np: str | None + testing: str | None + discovery_method: str | None
@@ -129,14 +130,14 @@ [docs] class IPSourceInfo(TypedDict): ip_address: str - country: Optional[str] - reverse_dns: Optional[str] - base_domain: Optional[str] - name: Optional[str] - type: Optional[str] - asn: Optional[int] - as_name: Optional[str] - as_domain: Optional[str]
+ country: str | None + reverse_dns: str | None + base_domain: str | None + name: str | None + type: str | None + asn: int | None + as_name: str | None + as_domain: str | None
@@ -153,16 +154,16 @@ [docs] class AggregateIdentifiers(TypedDict): header_from: str - envelope_from: Optional[str] - envelope_to: Optional[str]
+ envelope_from: str | None + envelope_to: str | None
[docs] class AggregatePolicyOverrideReason(TypedDict): - type: Optional[str] - comment: Optional[str]
+ type: str | None + comment: str | None
@@ -172,7 +173,7 @@ domain: str result: str selector: str - human_result: Optional[str]
+ human_result: str | None
@@ -182,15 +183,15 @@ domain: str result: str scope: str - human_result: Optional[str]
+ human_result: str | None
[docs] class AggregateAuthResults(TypedDict): - dkim: List[AggregateAuthResultDKIM] - spf: List[AggregateAuthResultSPF]
+ dkim: list[AggregateAuthResultDKIM] + spf: list[AggregateAuthResultSPF]
@@ -200,7 +201,7 @@ disposition: str dkim: str spf: str - policy_override_reasons: List[AggregatePolicyOverrideReason]
+ policy_override_reasons: list[AggregatePolicyOverrideReason]
@@ -223,29 +224,29 @@ [docs] class AggregateReport(TypedDict): xml_schema: str - xml_namespace: Optional[str] + xml_namespace: str | None report_metadata: AggregateReportMetadata policy_published: AggregatePolicyPublished - records: List[AggregateRecord]
+ records: list[AggregateRecord]
[docs] class EmailAddress(TypedDict): - display_name: Optional[str] + display_name: str | None address: str - local: Optional[str] - domain: Optional[str]
+ local: str | None + domain: str | None
[docs] class EmailAttachment(TypedDict, total=False): - filename: Optional[str] - mail_content_type: Optional[str] - sha256: Optional[str]
+ filename: str | None + mail_content_type: str | None + sha256: str | None
@@ -254,16 +255,16 @@ { # This is a lightly-specified version of mailsuite/mailparser JSON. # It focuses on the fields parsedmarc uses in failure report handling. - "headers": Dict[str, Any], - "subject": Optional[str], - "filename_safe_subject": Optional[str], - "date": Optional[str], + "headers": dict[str, Any], + "subject": str | None, + "filename_safe_subject": str | None, + "date": str | None, "from": EmailAddress, - "to": List[EmailAddress], - "cc": List[EmailAddress], - "bcc": List[EmailAddress], - "attachments": List[EmailAttachment], - "body": Optional[str], + "to": list[EmailAddress], + "cc": list[EmailAddress], + "bcc": list[EmailAddress], + "attachments": list[EmailAttachment], + "body": str | None, "has_defects": bool, "defects": Any, "defects_categories": Any, @@ -275,19 +276,19 @@
[docs] class FailureReport(TypedDict): - feedback_type: Optional[str] - user_agent: Optional[str] - version: Optional[str] - original_envelope_id: Optional[str] - original_mail_from: Optional[str] - original_rcpt_to: Optional[str] + feedback_type: str | None + user_agent: str | None + version: str | None + original_envelope_id: str | None + original_mail_from: str | None + original_rcpt_to: str | None arrival_date: str arrival_date_utc: str - authentication_results: Optional[str] - delivery_result: Optional[str] - auth_failure: List[str] - authentication_mechanisms: List[str] - dkim_domain: Optional[str] + authentication_results: str | None + delivery_result: str | None + auth_failure: list[str] + authentication_mechanisms: list[str] + dkim_domain: str | None reported_domain: str sample_headers_only: bool source: IPSourceInfo @@ -334,9 +335,9 @@
[docs] class SMTPTLSPolicy(SMTPTLSPolicySummary, total=False): - policy_strings: List[str] - mx_host_patterns: List[str] - failure_details: List[SMTPTLSFailureDetailsOptional]
+ policy_strings: list[str] + mx_host_patterns: list[str] + failure_details: list[SMTPTLSFailureDetailsOptional]
@@ -346,9 +347,9 @@ organization_name: str begin_date: str end_date: str - contact_info: Union[str, List[str]] + contact_info: str | list[str] report_id: str - policies: List[SMTPTLSPolicy] + policies: list[SMTPTLSPolicy] @@ -380,15 +381,15 @@ -ParsedReport = Union[AggregateParsedReport, FailureParsedReport, SMTPTLSParsedReport] +ParsedReport = AggregateParsedReport | FailureParsedReport | SMTPTLSParsedReport
[docs] class ParsingResults(TypedDict): - aggregate_reports: List[AggregateReport] - failure_reports: List[FailureReport] - smtp_tls_reports: List[SMTPTLSReport]
+ aggregate_reports: list[AggregateReport] + failure_reports: list[FailureReport] + smtp_tls_reports: list[SMTPTLSReport] diff --git a/_modules/parsedmarc/utils.html b/_modules/parsedmarc/utils.html index 1120492..44801f7 100644 --- a/_modules/parsedmarc/utils.html +++ b/_modules/parsedmarc/utils.html @@ -99,7 +99,7 @@ import subprocess import tempfile from datetime import datetime, timedelta, timezone -from typing import Optional, TypedDict, Union, cast +from typing import TypedDict, cast import mailparser from expiringdict import ExpiringDict @@ -151,8 +151,8 @@ def load_psl_overrides( *, always_use_local_file: bool = False, - local_file_path: Optional[str] = None, - url: Optional[str] = None, + local_file_path: str | None = None, + url: str | None = None, offline: bool = False, ) -> list[str]: """ @@ -231,7 +231,7 @@ [docs] class ReverseDNSService(TypedDict): name: str - type: Optional[str] + type: str | None @@ -242,14 +242,14 @@ [docs] class IPAddressInfo(TypedDict): ip_address: str - reverse_dns: Optional[str] - country: Optional[str] - base_domain: Optional[str] - name: Optional[str] - type: Optional[str] - asn: Optional[int] - as_name: Optional[str] - as_domain: Optional[str] + reverse_dns: str | None + country: str | None + base_domain: str | None + name: str | None + type: str | None + asn: int | None + as_name: str | None + as_domain: str | None @@ -276,7 +276,7 @@
[docs] -def get_base_domain(domain: str) -> Optional[str]: +def get_base_domain(domain: str) -> str | None: """ Gets the base domain name for the given domain @@ -307,8 +307,8 @@ domain: str, record_type: str, *, - cache: Optional[ExpiringDict] = None, - nameservers: Optional[list[str]] = None, + cache: ExpiringDict | None = None, + nameservers: list[str] | None = None, timeout: float = DEFAULT_DNS_TIMEOUT, retries: int = DEFAULT_DNS_MAX_RETRIES, _attempt: int = 0, @@ -402,11 +402,11 @@ def get_reverse_dns( ip_address, *, - cache: Optional[ExpiringDict] = None, - nameservers: Optional[list[str]] = None, + cache: ExpiringDict | None = None, + nameservers: list[str] | None = None, timeout: float = DEFAULT_DNS_TIMEOUT, retries: int = DEFAULT_DNS_MAX_RETRIES, -) -> Optional[str]: +) -> str | None: """ Resolves an IP address to a hostname using a reverse DNS query @@ -514,7 +514,7 @@ -_IP_DB_PATH: Optional[str] = None +_IP_DB_PATH: str | None = None
@@ -522,8 +522,8 @@ def load_ip_db( *, always_use_local_file: bool = False, - local_file_path: Optional[str] = None, - url: Optional[str] = None, + local_file_path: str | None = None, + url: str | None = None, offline: bool = False, ) -> None: """ @@ -585,10 +585,10 @@ class _IPDatabaseRecord(TypedDict): - country: Optional[str] - asn: Optional[int] - as_name: Optional[str] - as_domain: Optional[str] + country: str | None + asn: int | None + as_name: str | None + as_domain: str | None
@@ -608,14 +608,14 @@ # here — adding it would be inventing behavior the service doesn't document. # Authentication uses the documented ``?token=`` query parameter. _IPINFO_API_URL = "https://api.ipinfo.io/lite" -_IPINFO_API_TOKEN: Optional[str] = None +_IPINFO_API_TOKEN: str | None = None _IPINFO_API_TIMEOUT: float = 5.0
[docs] def configure_ipinfo_api( - token: Optional[str], + token: str | None, *, probe: bool = True, ) -> None: @@ -650,7 +650,7 @@ -def _ipinfo_api_lookup(ip_address: str) -> Optional[_IPDatabaseRecord]: +def _ipinfo_api_lookup(ip_address: str) -> _IPDatabaseRecord | None: """Look up an IP via the IPinfo Lite REST API. Returns the normalized record on success, or ``None`` on network error or @@ -699,10 +699,10 @@ same output: country as ISO code, ASN as plain int, as_name string, as_domain lowercased. """ - country: Optional[str] = None - asn: Optional[int] = None - as_name: Optional[str] = None - as_domain: Optional[str] = None + country: str | None = None + asn: int | None = None + as_name: str | None = None + as_domain: str | None = None code = record.get("country_code") if code is None: @@ -739,7 +739,7 @@ } -def _get_ip_database_path(db_path: Optional[str]) -> str: +def _get_ip_database_path(db_path: str | None) -> str: db_paths = [ "ipinfo_lite.mmdb", "GeoLite2-Country.mmdb", @@ -787,7 +787,7 @@
[docs] def get_ip_address_db_record( - ip_address: str, *, db_path: Optional[str] = None + ip_address: str, *, db_path: str | None = None ) -> _IPDatabaseRecord: """Look up an IP and return country + ASN fields. @@ -821,8 +821,8 @@
[docs] def get_ip_address_country( - ip_address: str, *, db_path: Optional[str] = None -) -> Optional[str]: + ip_address: str, *, db_path: str | None = None +) -> str | None: """ Returns the ISO code for the country associated with the given IPv4 or IPv6 address. @@ -844,11 +844,11 @@ reverse_dns_map: ReverseDNSMap, *, always_use_local_file: bool = False, - local_file_path: Optional[str] = None, - url: Optional[str] = None, + local_file_path: str | None = None, + url: str | None = None, offline: bool = False, - psl_overrides_path: Optional[str] = None, - psl_overrides_url: Optional[str] = None, + psl_overrides_path: str | None = None, + psl_overrides_url: str | None = None, ) -> None: """ Loads the reverse DNS map from a URL or local file. @@ -935,10 +935,10 @@ base_domain, *, always_use_local_file: bool = False, - local_file_path: Optional[str] = None, - url: Optional[str] = None, + local_file_path: str | None = None, + url: str | None = None, offline: bool = False, - reverse_dns_map: Optional[ReverseDNSMap] = None, + reverse_dns_map: ReverseDNSMap | None = None, ) -> ReverseDNSService: """ Returns the service name of a given base domain name from reverse DNS. @@ -987,14 +987,14 @@ def get_ip_address_info( ip_address, *, - ip_db_path: Optional[str] = None, - reverse_dns_map_path: Optional[str] = None, + ip_db_path: str | None = None, + reverse_dns_map_path: str | None = None, always_use_local_files: bool = False, - reverse_dns_map_url: Optional[str] = None, - cache: Optional[ExpiringDict] = None, - reverse_dns_map: Optional[ReverseDNSMap] = None, + reverse_dns_map_url: str | None = None, + cache: ExpiringDict | None = None, + reverse_dns_map: ReverseDNSMap | None = None, offline: bool = False, - nameservers: Optional[list[str]] = None, + nameservers: list[str] | None = None, timeout: float = DEFAULT_DNS_TIMEOUT, retries: int = DEFAULT_DNS_MAX_RETRIES, ) -> IPAddressInfo: @@ -1119,7 +1119,7 @@ -def parse_email_address(original_address: str) -> dict[str, Optional[str]]: +def parse_email_address(original_address: str) -> dict[str, str | None]: if original_address[0] == "": display_name = None else: @@ -1248,9 +1248,7 @@
[docs] -def parse_email( - data: Union[bytes, str], *, strip_attachment_payloads: bool = False -) -> dict: +def parse_email(data: bytes | str, *, strip_attachment_payloads: bool = False) -> dict: """ A simplified email parser