From fc3a13147d6b79c76ff8dd896caff34ecd77119f Mon Sep 17 00:00:00 2001 From: Sean Whalen Date: Mon, 21 Oct 2024 18:34:13 -0400 Subject: [PATCH] Update docs --- _modules/index.html | 28 +- _modules/parsedmarc.html | 1033 ++++++++++------- _modules/parsedmarc/elastic.html | 470 ++++---- _modules/parsedmarc/opensearch.html | 464 ++++---- _modules/parsedmarc/splunk.html | 113 +- _modules/parsedmarc/utils.html | 298 +++-- _sources/usage.md.txt | 18 + .../_sphinx_javascript_frameworks_compat.js | 17 +- _static/basic.css | 83 +- _static/css/badge_only.css | 2 +- _static/css/theme.css | 2 +- _static/doctools.js | 7 - _static/documentation_options.js | 5 +- _static/js/versions.js | 224 ++++ _static/language_data.js | 9 +- _static/searchtools.js | 226 ++-- _static/sphinx_highlight.js | 16 +- api.html | 402 ++++--- contributing.html | 34 +- davmail.html | 36 +- dmarc.html | 97 +- elasticsearch.html | 44 +- genindex.html | 28 +- index.html | 37 +- installation.html | 46 +- kibana.html | 36 +- mailing-lists.html | 78 +- objects.inv | Bin 1107 -> 1107 bytes opensearch.html | 36 +- output.html | 46 +- py-modindex.html | 28 +- search.html | 28 +- searchindex.js | 2 +- splunk.html | 32 +- usage.html | 60 +- 35 files changed, 2323 insertions(+), 1762 deletions(-) create mode 100644 _static/js/versions.js diff --git a/_modules/index.html b/_modules/index.html index 3d20908b..a59832ec 100644 --- a/_modules/index.html +++ b/_modules/index.html @@ -1,23 +1,20 @@ + + - + - Overview: module code — parsedmarc 8.15.0 documentation - - + Overview: module code — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
diff --git a/_modules/parsedmarc.html b/_modules/parsedmarc.html index 9fe2d2b4..d45bc84c 100644 --- a/_modules/parsedmarc.html +++ b/_modules/parsedmarc.html @@ -1,23 +1,20 @@ + + - + - parsedmarc — parsedmarc 8.15.0 documentation - - + parsedmarc — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -121,7 +115,7 @@

Source code for parsedmarc

 from parsedmarc.utils import parse_email
 from parsedmarc.utils import timestamp_to_human, human_timestamp_to_datetime
 
-__version__ = "8.15.0"
+__version__ = "8.15.1"
 
 logger.debug("parsedmarc v{0}".format(__version__))
 
@@ -130,8 +124,8 @@ 

Source code for parsedmarc

 xml_schema_regex = re.compile(r"</??xs:schema.*>", re.MULTILINE)
 text_report_regex = re.compile(r"\s*([a-zA-Z\s]+):\s(.+)", re.MULTILINE)
 
-MAGIC_ZIP = b"\x50\x4B\x03\x04"
-MAGIC_GZIP = b"\x1F\x8B"
+MAGIC_ZIP = b"\x50\x4b\x03\x04"
+MAGIC_GZIP = b"\x1f\x8b"
 MAGIC_XML = b"\x3c\x3f\x78\x6d\x6c\x20"
 MAGIC_JSON = b"\7b"
 
@@ -139,32 +133,51 @@ 

Source code for parsedmarc

 REVERSE_DNS_MAP = dict()
 
 
-
[docs]class ParserError(RuntimeError): +
+[docs] +class ParserError(RuntimeError): """Raised whenever the parser fails for some reason"""
-
[docs]class InvalidDMARCReport(ParserError): + +
+[docs] +class InvalidDMARCReport(ParserError): """Raised when an invalid DMARC report is encountered"""
-
[docs]class InvalidSMTPTLSReport(ParserError): + +
+[docs] +class InvalidSMTPTLSReport(ParserError): """Raised when an invalid SMTP TLS report is encountered"""
-
[docs]class InvalidAggregateReport(InvalidDMARCReport): + +
+[docs] +class InvalidAggregateReport(InvalidDMARCReport): """Raised when an invalid DMARC aggregate report is encountered"""
-
[docs]class InvalidForensicReport(InvalidDMARCReport): + +
+[docs] +class InvalidForensicReport(InvalidDMARCReport): """Raised when an invalid DMARC forensic report is encountered"""
-def _parse_report_record(record, ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, - nameservers=None, dns_timeout=2.0): + +def _parse_report_record( + record, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + nameservers=None, + dns_timeout=2.0, +): """ Converts a record from a DMARC aggregate report into a more consistent format @@ -197,15 +210,19 @@

Source code for parsedmarc

         reverse_dns_map=REVERSE_DNS_MAP,
         offline=offline,
         nameservers=nameservers,
-        timeout=dns_timeout)
+        timeout=dns_timeout,
+    )
     new_record["source"] = new_record_source
     new_record["count"] = int(record["row"]["count"])
     policy_evaluated = record["row"]["policy_evaluated"].copy()
-    new_policy_evaluated = OrderedDict([("disposition", "none"),
-                                        ("dkim", "fail"),
-                                        ("spf", "fail"),
-                                        ("policy_override_reasons", [])
-                                        ])
+    new_policy_evaluated = OrderedDict(
+        [
+            ("disposition", "none"),
+            ("dkim", "fail"),
+            ("spf", "fail"),
+            ("policy_override_reasons", []),
+        ]
+    )
     if "disposition" in policy_evaluated:
         new_policy_evaluated["disposition"] = policy_evaluated["disposition"]
         if new_policy_evaluated["disposition"].strip().lower() == "pass":
@@ -215,10 +232,14 @@ 

Source code for parsedmarc

     if "spf" in policy_evaluated:
         new_policy_evaluated["spf"] = policy_evaluated["spf"]
     reasons = []
-    spf_aligned = policy_evaluated["spf"] is not None and policy_evaluated[
-        "spf"].lower() == "pass"
-    dkim_aligned = policy_evaluated["dkim"] is not None and policy_evaluated[
-        "dkim"].lower() == "pass"
+    spf_aligned = (
+        policy_evaluated["spf"] is not None
+        and policy_evaluated["spf"].lower() == "pass"
+    )
+    dkim_aligned = (
+        policy_evaluated["dkim"] is not None
+        and policy_evaluated["dkim"].lower() == "pass"
+    )
     dmarc_aligned = spf_aligned or dkim_aligned
     new_record["alignment"] = dict()
     new_record["alignment"]["spf"] = spf_aligned
@@ -242,7 +263,7 @@ 

Source code for parsedmarc

     if type(new_record["identifiers"]["header_from"]) is str:
         lowered_from = new_record["identifiers"]["header_from"].lower()
     else:
-        lowered_from = ''
+        lowered_from = ""
     new_record["identifiers"]["header_from"] = lowered_from
     if record["auth_results"] is not None:
         auth_results = record["auth_results"].copy()
@@ -318,29 +339,30 @@ 

Source code for parsedmarc

         )
 
         if "sending-mta-ip" in failure_details:
-            new_failure_details["sending_mta_ip"] = failure_details[
-                "sending-mta-ip"]
+            new_failure_details["sending_mta_ip"] = failure_details["sending-mta-ip"]
         if "receiving-ip" in failure_details:
-            new_failure_details["receiving_ip"] = failure_details[
-                "receiving-ip"]
+            new_failure_details["receiving_ip"] = failure_details["receiving-ip"]
         if "receiving-mx-hostname" in failure_details:
             new_failure_details["receiving_mx_hostname"] = failure_details[
-                "receiving-mx-hostname"]
+                "receiving-mx-hostname"
+            ]
         if "receiving-mx-helo" in failure_details:
             new_failure_details["receiving_mx_helo"] = failure_details[
-                "receiving-mx-helo"]
+                "receiving-mx-helo"
+            ]
         if "additional-info-uri" in failure_details:
             new_failure_details["additional_info_uri"] = failure_details[
-                "additional-info-uri"]
+                "additional-info-uri"
+            ]
         if "failure-reason-code" in failure_details:
             new_failure_details["failure_reason_code"] = failure_details[
-                "failure-reason-code"]
+                "failure-reason-code"
+            ]
 
         return new_failure_details
 
     except KeyError as e:
-        raise InvalidSMTPTLSReport(f"Missing required failure details field:"
-                                   f" {e}")
+        raise InvalidSMTPTLSReport(f"Missing required failure details field:" f" {e}")
     except Exception as e:
         raise InvalidSMTPTLSReport(str(e))
 
@@ -352,29 +374,26 @@ 

Source code for parsedmarc

         policy_type = policy["policy"]["policy-type"]
         failure_details = []
         if policy_type not in policy_types:
-            raise InvalidSMTPTLSReport(f"Invalid policy type "
-                                       f"{policy_type}")
-        new_policy = OrderedDict(policy_domain=policy_domain,
-                                 policy_type=policy_type)
+            raise InvalidSMTPTLSReport(f"Invalid policy type " f"{policy_type}")
+        new_policy = OrderedDict(policy_domain=policy_domain, policy_type=policy_type)
         if "policy-string" in policy["policy"]:
             if isinstance(policy["policy"]["policy-string"], list):
                 if len(policy["policy"]["policy-string"]) > 0:
-                    new_policy["policy_strings"] = policy["policy"][
-                        "policy-string"]
+                    new_policy["policy_strings"] = policy["policy"]["policy-string"]
 
         if "mx-host-pattern" in policy["policy"]:
             if isinstance(policy["policy"]["mx-host-pattern"], list):
                 if len(policy["policy"]["mx-host-pattern"]) > 0:
-                    new_policy["mx_host_patterns"] = policy["policy"][
-                        "mx-host-pattern"]
+                    new_policy["mx_host_patterns"] = policy["policy"]["mx-host-pattern"]
         new_policy["successful_session_count"] = policy["summary"][
-            "total-successful-session-count"]
+            "total-successful-session-count"
+        ]
         new_policy["failed_session_count"] = policy["summary"][
-            "total-failure-session-count"]
+            "total-failure-session-count"
+        ]
         if "failure-details" in policy:
             for details in policy["failure-details"]:
-                failure_details.append(_parse_smtp_tls_failure_details(
-                    details))
+                failure_details.append(_parse_smtp_tls_failure_details(details))
             new_policy["failure_details"] = failure_details
 
         return new_policy
@@ -385,11 +404,17 @@ 

Source code for parsedmarc

         raise InvalidSMTPTLSReport(str(e))
 
 
-
[docs]def parse_smtp_tls_report_json(report): +
+[docs] +def parse_smtp_tls_report_json(report): """Parses and validates an SMTP TLS report""" - required_fields = ["organization-name", "date-range", - "contact-info", "report-id", - "policies"] + required_fields = [ + "organization-name", + "date-range", + "contact-info", + "report-id", + "policies", + ] try: policies = [] @@ -399,8 +424,9 @@

Source code for parsedmarc

                 raise Exception(f"Missing required field: {required_field}]")
         if not isinstance(report["policies"], list):
             policies_type = type(report["policies"])
-            raise InvalidSMTPTLSReport(f"policies must be a list, "
-                                       f"not {policies_type}")
+            raise InvalidSMTPTLSReport(
+                f"policies must be a list, " f"not {policies_type}"
+            )
         for policy in report["policies"]:
             policies.append(_parse_smtp_tls_report_policy(policy))
 
@@ -410,7 +436,7 @@ 

Source code for parsedmarc

             end_date=report["date-range"]["end-datetime"],
             contact_info=report["contact-info"],
             report_id=report["report-id"],
-            policies=policies
+            policies=policies,
         )
 
         return new_report
@@ -421,7 +447,10 @@ 

Source code for parsedmarc

         raise InvalidSMTPTLSReport(str(e))
-
[docs]def parsed_smtp_tls_reports_to_csv_rows(reports): + +
+[docs] +def parsed_smtp_tls_reports_to_csv_rows(reports): """Converts one oor more parsed SMTP TLS reports into a list of single layer OrderedDict objects suitable for use in a CSV""" if type(reports) is OrderedDict: @@ -433,18 +462,18 @@

Source code for parsedmarc

             organization_name=report["organization_name"],
             begin_date=report["begin_date"],
             end_date=report["end_date"],
-            report_id=report["report_id"]
+            report_id=report["report_id"],
         )
         record = common_fields.copy()
         for policy in report["policies"]:
             if "policy_strings" in policy:
                 record["policy_strings"] = "|".join(policy["policy_strings"])
             if "mx_host_patterns" in policy:
-                record["mx_host_patterns"] = "|".join(
-                    policy["mx_host_patterns"])
+                record["mx_host_patterns"] = "|".join(policy["mx_host_patterns"])
             successful_record = record.copy()
             successful_record["successful_session_count"] = policy[
-                "successful_session_count"]
+                "successful_session_count"
+            ]
             rows.append(successful_record)
             if "failure_details" in policy:
                 for failure_details in policy["failure_details"]:
@@ -456,7 +485,10 @@ 

Source code for parsedmarc

     return rows
-
[docs]def parsed_smtp_tls_reports_to_csv(reports): + +
+[docs] +def parsed_smtp_tls_reports_to_csv(reports): """ Converts one or more parsed SMTP TLS reports to flat CSV format, including headers @@ -468,12 +500,25 @@

Source code for parsedmarc

         str: Parsed aggregate report data in flat CSV format, including headers
     """
 
-    fields = ["organization_name", "begin_date", "end_date", "report_id",
-              "result_type", "successful_session_count",
-              "failed_session_count", "policy_domain", "policy_type",
-              "policy_strings", "mx_host_patterns", "sending_mta_ip",
-              "receiving_ip", "receiving_mx_hostname", "receiving_mx_helo",
-              "additional_info_uri", "failure_reason_code"]
+    fields = [
+        "organization_name",
+        "begin_date",
+        "end_date",
+        "report_id",
+        "result_type",
+        "successful_session_count",
+        "failed_session_count",
+        "policy_domain",
+        "policy_type",
+        "policy_strings",
+        "mx_host_patterns",
+        "sending_mta_ip",
+        "receiving_ip",
+        "receiving_mx_hostname",
+        "receiving_mx_helo",
+        "additional_info_uri",
+        "failure_reason_code",
+    ]
 
     csv_file_object = StringIO(newline="\n")
     writer = DictWriter(csv_file_object, fields)
@@ -488,16 +533,20 @@ 

Source code for parsedmarc

     return csv_file_object.getvalue()
-
[docs]def parse_aggregate_report_xml( - xml, - ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, - nameservers=None, - timeout=2.0, - keep_alive=None): + +
+[docs] +def parse_aggregate_report_xml( + xml, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + nameservers=None, + timeout=2.0, + keep_alive=None, +): """Parses a DMARC XML report string and returns a consistent OrderedDict Args: @@ -518,26 +567,27 @@

Source code for parsedmarc

     errors = []
     # Parse XML and recover from errors
     if isinstance(xml, bytes):
-        xml = xml.decode(errors='ignore')
+        xml = xml.decode(errors="ignore")
     try:
         xmltodict.parse(xml)["feedback"]
     except Exception as e:
         errors.append("Invalid XML: {0}".format(e.__str__()))
         try:
             tree = etree.parse(
-                BytesIO(xml.encode('utf-8')),
-                etree.XMLParser(recover=True, resolve_entities=False))
+                BytesIO(xml.encode("utf-8")),
+                etree.XMLParser(recover=True, resolve_entities=False),
+            )
             s = etree.tostring(tree)
-            xml = '' if s is None else s.decode('utf-8')
+            xml = "" if s is None else s.decode("utf-8")
         except Exception:
-            xml = u'<a/>'
+            xml = "<a/>"
 
     try:
         # Replace XML header (sometimes they are invalid)
-        xml = xml_header_regex.sub("<?xml version=\"1.0\"?>", xml)
+        xml = xml_header_regex.sub('<?xml version="1.0"?>', xml)
 
         # Remove invalid schema tags
-        xml = xml_schema_regex.sub('', xml)
+        xml = xml_schema_regex.sub("", xml)
 
         report = xmltodict.parse(xml)["feedback"]
         report_metadata = report["report_metadata"]
@@ -548,20 +598,21 @@ 

Source code for parsedmarc

         new_report_metadata = OrderedDict()
         if report_metadata["org_name"] is None:
             if report_metadata["email"] is not None:
-                report_metadata["org_name"] = report_metadata[
-                    "email"].split("@")[-1]
+                report_metadata["org_name"] = report_metadata["email"].split("@")[-1]
         org_name = report_metadata["org_name"]
         if org_name is not None and " " not in org_name:
             new_org_name = get_base_domain(org_name)
             if new_org_name is not None:
                 org_name = new_org_name
         if not org_name:
-            logger.debug("Could not parse org_name from XML.\r\n{0}".format(
-                report.__str__()
-            ))
-            raise KeyError("Organization name is missing. \
+            logger.debug(
+                "Could not parse org_name from XML.\r\n{0}".format(report.__str__())
+            )
+            raise KeyError(
+                "Organization name is missing. \
                            This field is a requirement for \
-                           saving the report")
+                           saving the report"
+            )
         new_report_metadata["org_name"] = org_name
         new_report_metadata["org_email"] = report_metadata["email"]
         extra = None
@@ -570,11 +621,10 @@ 

Source code for parsedmarc

         new_report_metadata["org_extra_contact_info"] = extra
         new_report_metadata["report_id"] = report_metadata["report_id"]
         report_id = new_report_metadata["report_id"]
-        report_id = report_id.replace("<",
-                                      "").replace(">", "").split("@")[0]
+        report_id = report_id.replace("<", "").replace(">", "").split("@")[0]
         new_report_metadata["report_id"] = report_id
         date_range = report["report_metadata"]["date_range"]
-        if int(date_range["end"]) - int(date_range["begin"]) > 2*86400:
+        if int(date_range["end"]) - int(date_range["begin"]) > 2 * 86400:
             _error = "Time span > 24 hours - RFC 7489 section 7.2"
             errors.append(_error)
         date_range["begin"] = timestamp_to_human(date_range["begin"])
@@ -590,6 +640,8 @@ 

Source code for parsedmarc

         new_report["report_metadata"] = new_report_metadata
         records = []
         policy_published = report["policy_published"]
+        if type(policy_published) is list:
+            policy_published = policy_published[0]
         new_policy_published = OrderedDict()
         new_policy_published["domain"] = policy_published["domain"]
         adkim = "r"
@@ -606,17 +658,17 @@ 

Source code for parsedmarc

         sp = new_policy_published["p"]
         if "sp" in policy_published:
             if policy_published["sp"] is not None:
-                sp = report["policy_published"]["sp"]
+                sp = policy_published["sp"]
         new_policy_published["sp"] = sp
         pct = "100"
         if "pct" in policy_published:
             if policy_published["pct"] is not None:
-                pct = report["policy_published"]["pct"]
+                pct = policy_published["pct"]
         new_policy_published["pct"] = pct
         fo = "0"
         if "fo" in policy_published:
             if policy_published["fo"] is not None:
-                fo = report["policy_published"]["fo"]
+                fo = policy_published["fo"]
         new_policy_published["fo"] = fo
         new_report["policy_published"] = new_policy_published
 
@@ -625,8 +677,7 @@ 

Source code for parsedmarc

                 if keep_alive is not None and i > 0 and i % 20 == 0:
                     logger.debug("Sending keepalive cmd")
                     keep_alive()
-                    logger.debug("Processed {0}/{1}".format(
-                        i, len(report["record"])))
+                    logger.debug("Processed {0}/{1}".format(i, len(report["record"])))
                 try:
                     report_record = _parse_report_record(
                         report["record"][i],
@@ -636,7 +687,8 @@ 

Source code for parsedmarc

                         reverse_dns_map_path=reverse_dns_map_path,
                         reverse_dns_map_url=reverse_dns_map_url,
                         nameservers=nameservers,
-                        dns_timeout=timeout)
+                        dns_timeout=timeout,
+                    )
                     records.append(report_record)
                 except Exception as e:
                     logger.warning("Could not parse record: {0}".format(e))
@@ -650,7 +702,8 @@ 

Source code for parsedmarc

                 reverse_dns_map_url=reverse_dns_map_url,
                 offline=offline,
                 nameservers=nameservers,
-                dns_timeout=timeout)
+                dns_timeout=timeout,
+            )
             records.append(report_record)
 
         new_report["records"] = records
@@ -658,21 +711,21 @@ 

Source code for parsedmarc

         return new_report
 
     except expat.ExpatError as error:
-        raise InvalidAggregateReport(
-            "Invalid XML: {0}".format(error.__str__()))
+        raise InvalidAggregateReport("Invalid XML: {0}".format(error.__str__()))
 
     except KeyError as error:
-        raise InvalidAggregateReport(
-            "Missing field: {0}".format(error.__str__()))
+        raise InvalidAggregateReport("Missing field: {0}".format(error.__str__()))
     except AttributeError:
         raise InvalidAggregateReport("Report missing required section")
 
     except Exception as error:
-        raise InvalidAggregateReport(
-            "Unexpected error: {0}".format(error.__str__()))
+ raise InvalidAggregateReport("Unexpected error: {0}".format(error.__str__()))
+ -
[docs]def extract_report(content): +
+[docs] +def extract_report(content): """ Extracts text from a zip or gzip file, as a base64-encoded string, file-like object, or bytes. @@ -703,14 +756,13 @@

Source code for parsedmarc

         file_object.seek(0)
         if header.startswith(MAGIC_ZIP):
             _zip = zipfile.ZipFile(file_object)
-            report = _zip.open(_zip.namelist()[0]).read().decode(
-                errors='ignore')
+            report = _zip.open(_zip.namelist()[0]).read().decode(errors="ignore")
         elif header.startswith(MAGIC_GZIP):
-            report = zlib.decompress(
-                file_object.read(),
-                zlib.MAX_WBITS | 16).decode(errors='ignore')
+            report = zlib.decompress(file_object.read(), zlib.MAX_WBITS | 16).decode(
+                errors="ignore"
+            )
         elif header.startswith(MAGIC_XML) or header.startswith(MAGIC_JSON):
-            report = file_object.read().decode(errors='ignore')
+            report = file_object.read().decode(errors="ignore")
         else:
             file_object.close()
             raise ParserError("Not a valid zip, gzip, json, or xml file")
@@ -722,13 +774,15 @@ 

Source code for parsedmarc

         raise ParserError("File objects must be opened in binary (rb) mode")
     except Exception as error:
         file_object.close()
-        raise ParserError(
-            "Invalid archive file: {0}".format(error.__str__()))
+        raise ParserError("Invalid archive file: {0}".format(error.__str__()))
 
     return report
-
[docs]def extract_report_from_file_path(file_path): + +
+[docs] +def extract_report_from_file_path(file_path): """Extracts report from a file at the given file_path""" try: with open(file_path, "rb") as report_file: @@ -737,16 +791,20 @@

Source code for parsedmarc

         raise ParserError("File was not found")
-
[docs]def parse_aggregate_report_file( - _input, - offline=False, - always_use_local_files=None, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - ip_db_path=None, - nameservers=None, - dns_timeout=2.0, - keep_alive=None): + +
+[docs] +def parse_aggregate_report_file( + _input, + offline=False, + always_use_local_files=None, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + ip_db_path=None, + nameservers=None, + dns_timeout=2.0, + keep_alive=None, +): """Parses a file at the given path, a file-like object. or bytes as an aggregate DMARC report @@ -780,10 +838,14 @@

Source code for parsedmarc

         offline=offline,
         nameservers=nameservers,
         timeout=dns_timeout,
-        keep_alive=keep_alive)
+ keep_alive=keep_alive, + )
+ -
[docs]def parsed_aggregate_reports_to_csv_rows(reports): +
+[docs] +def parsed_aggregate_reports_to_csv_rows(reports): """ Converts one or more parsed aggregate reports to list of dicts in flat CSV format @@ -821,12 +883,23 @@

Source code for parsedmarc

         pct = report["policy_published"]["pct"]
         fo = report["policy_published"]["fo"]
 
-        report_dict = dict(xml_schema=xml_schema, org_name=org_name,
-                           org_email=org_email,
-                           org_extra_contact_info=org_extra_contact,
-                           report_id=report_id, begin_date=begin_date,
-                           end_date=end_date, errors=errors, domain=domain,
-                           adkim=adkim, aspf=aspf, p=p, sp=sp, pct=pct, fo=fo)
+        report_dict = dict(
+            xml_schema=xml_schema,
+            org_name=org_name,
+            org_email=org_email,
+            org_extra_contact_info=org_extra_contact,
+            report_id=report_id,
+            begin_date=begin_date,
+            end_date=end_date,
+            errors=errors,
+            domain=domain,
+            adkim=adkim,
+            aspf=aspf,
+            p=p,
+            sp=sp,
+            pct=pct,
+            fo=fo,
+        )
 
         for record in report["records"]:
             row = report_dict.copy()
@@ -841,18 +914,20 @@ 

Source code for parsedmarc

             row["dkim_aligned"] = record["alignment"]["dkim"]
             row["dmarc_aligned"] = record["alignment"]["dmarc"]
             row["disposition"] = record["policy_evaluated"]["disposition"]
-            policy_override_reasons = list(map(
-                lambda r_: r_["type"] or "none",
-                record["policy_evaluated"]
-                ["policy_override_reasons"]))
-            policy_override_comments = list(map(
-                lambda r_: r_["comment"] or "none",
-                record["policy_evaluated"]
-                ["policy_override_reasons"]))
-            row["policy_override_reasons"] = ",".join(
-                policy_override_reasons)
-            row["policy_override_comments"] = "|".join(
-                policy_override_comments)
+            policy_override_reasons = list(
+                map(
+                    lambda r_: r_["type"] or "none",
+                    record["policy_evaluated"]["policy_override_reasons"],
+                )
+            )
+            policy_override_comments = list(
+                map(
+                    lambda r_: r_["comment"] or "none",
+                    record["policy_evaluated"]["policy_override_reasons"],
+                )
+            )
+            row["policy_override_reasons"] = ",".join(policy_override_reasons)
+            row["policy_override_comments"] = "|".join(policy_override_comments)
             row["envelope_from"] = record["identifiers"]["envelope_from"]
             row["header_from"] = record["identifiers"]["header_from"]
             envelope_to = record["identifiers"]["envelope_to"]
@@ -883,12 +958,15 @@ 

Source code for parsedmarc

     for r in rows:
         for k, v in r.items():
             if type(v) not in [str, int, bool]:
-                r[k] = ''
+                r[k] = ""
 
     return rows
-
[docs]def parsed_aggregate_reports_to_csv(reports): + +
+[docs] +def parsed_aggregate_reports_to_csv(reports): """ Converts one or more parsed aggregate reports to flat CSV format, including headers @@ -900,16 +978,45 @@

Source code for parsedmarc

         str: Parsed aggregate report data in flat CSV format, including headers
     """
 
-    fields = ["xml_schema", "org_name", "org_email",
-              "org_extra_contact_info", "report_id", "begin_date", "end_date",
-              "errors", "domain", "adkim", "aspf", "p", "sp", "pct", "fo",
-              "source_ip_address", "source_country", "source_reverse_dns",
-              "source_base_domain", "source_name", "source_type", "count",
-              "spf_aligned", "dkim_aligned", "dmarc_aligned", "disposition",
-              "policy_override_reasons",  "policy_override_comments",
-              "envelope_from", "header_from",
-              "envelope_to", "dkim_domains", "dkim_selectors", "dkim_results",
-              "spf_domains", "spf_scopes", "spf_results"]
+    fields = [
+        "xml_schema",
+        "org_name",
+        "org_email",
+        "org_extra_contact_info",
+        "report_id",
+        "begin_date",
+        "end_date",
+        "errors",
+        "domain",
+        "adkim",
+        "aspf",
+        "p",
+        "sp",
+        "pct",
+        "fo",
+        "source_ip_address",
+        "source_country",
+        "source_reverse_dns",
+        "source_base_domain",
+        "source_name",
+        "source_type",
+        "count",
+        "spf_aligned",
+        "dkim_aligned",
+        "dmarc_aligned",
+        "disposition",
+        "policy_override_reasons",
+        "policy_override_comments",
+        "envelope_from",
+        "header_from",
+        "envelope_to",
+        "dkim_domains",
+        "dkim_selectors",
+        "dkim_results",
+        "spf_domains",
+        "spf_scopes",
+        "spf_results",
+    ]
 
     csv_file_object = StringIO(newline="\n")
     writer = DictWriter(csv_file_object, fields)
@@ -924,17 +1031,22 @@ 

Source code for parsedmarc

     return csv_file_object.getvalue()
-
[docs]def parse_forensic_report(feedback_report, - sample, - msg_date, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, - ip_db_path=None, - nameservers=None, - dns_timeout=2.0, - strip_attachment_payloads=False): + +
+[docs] +def parse_forensic_report( + feedback_report, + sample, + msg_date, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + ip_db_path=None, + nameservers=None, + dns_timeout=2.0, + strip_attachment_payloads=False, +): """ Converts a DMARC forensic report and sample to a ``OrderedDict`` @@ -967,8 +1079,7 @@

Source code for parsedmarc

 
         if "arrival_date" not in parsed_report:
             if msg_date is None:
-                raise InvalidForensicReport(
-                    "Forensic sample is not a valid email")
+                raise InvalidForensicReport("Forensic sample is not a valid email")
             parsed_report["arrival_date"] = msg_date.isoformat()
 
         if "version" not in parsed_report:
@@ -988,11 +1099,12 @@ 

Source code for parsedmarc

             parsed_report["delivery_result"] = "other"
 
         arrival_utc = human_timestamp_to_datetime(
-            parsed_report["arrival_date"], to_utc=True)
+            parsed_report["arrival_date"], to_utc=True
+        )
         arrival_utc = arrival_utc.strftime("%Y-%m-%d %H:%M:%S")
         parsed_report["arrival_date_utc"] = arrival_utc
 
-        ip_address = re.split(r'\s', parsed_report["source_ip"]).pop(0)
+        ip_address = re.split(r"\s", parsed_report["source_ip"]).pop(0)
         parsed_report_source = get_ip_address_info(
             ip_address,
             cache=IP_ADDRESS_CACHE,
@@ -1003,7 +1115,8 @@ 

Source code for parsedmarc

             reverse_dns_map=REVERSE_DNS_MAP,
             offline=offline,
             nameservers=nameservers,
-            timeout=dns_timeout)
+            timeout=dns_timeout,
+        )
         parsed_report["source"] = parsed_report_source
         del parsed_report["source_ip"]
 
@@ -1023,15 +1136,19 @@ 

Source code for parsedmarc

         auth_failure = parsed_report["auth_failure"].split(",")
         parsed_report["auth_failure"] = auth_failure
 
-        optional_fields = ["original_envelope_id", "dkim_domain",
-                           "original_mail_from", "original_rcpt_to"]
+        optional_fields = [
+            "original_envelope_id",
+            "dkim_domain",
+            "original_mail_from",
+            "original_rcpt_to",
+        ]
         for optional_field in optional_fields:
             if optional_field not in parsed_report:
                 parsed_report[optional_field] = None
 
         parsed_sample = parse_email(
-            sample,
-            strip_attachment_payloads=strip_attachment_payloads)
+            sample, strip_attachment_payloads=strip_attachment_payloads
+        )
 
         if "reported_domain" not in parsed_report:
             parsed_report["reported_domain"] = parsed_sample["from"]["domain"]
@@ -1051,15 +1168,16 @@ 

Source code for parsedmarc

         return parsed_report
 
     except KeyError as error:
-        raise InvalidForensicReport("Missing value: {0}".format(
-            error.__str__()))
+        raise InvalidForensicReport("Missing value: {0}".format(error.__str__()))
 
     except Exception as error:
-        raise InvalidForensicReport(
-            "Unexpected error: {0}".format(error.__str__()))
+ raise InvalidForensicReport("Unexpected error: {0}".format(error.__str__()))
+ -
[docs]def parsed_forensic_reports_to_csv_rows(reports): +
+[docs] +def parsed_forensic_reports_to_csv_rows(reports): """ Converts one or more parsed forensic reports to a list of dicts in flat CSV format @@ -1087,8 +1205,7 @@

Source code for parsedmarc

         row["subject"] = report["parsed_sample"]["subject"]
         row["auth_failure"] = ",".join(report["auth_failure"])
         authentication_mechanisms = report["authentication_mechanisms"]
-        row["authentication_mechanisms"] = ",".join(
-            authentication_mechanisms)
+        row["authentication_mechanisms"] = ",".join(authentication_mechanisms)
         del row["sample"]
         del row["parsed_sample"]
         rows.append(row)
@@ -1096,7 +1213,10 @@ 

Source code for parsedmarc

     return rows
-
[docs]def parsed_forensic_reports_to_csv(reports): + +
+[docs] +def parsed_forensic_reports_to_csv(reports): """ Converts one or more parsed forensic reports to flat CSV format, including headers @@ -1107,14 +1227,31 @@

Source code for parsedmarc

     Returns:
         str: Parsed forensic report data in flat CSV format, including headers
     """
-    fields = ["feedback_type", "user_agent", "version", "original_envelope_id",
-              "original_mail_from", "original_rcpt_to", "arrival_date",
-              "arrival_date_utc", "subject", "message_id",
-              "authentication_results", "dkim_domain", "source_ip_address",
-              "source_country", "source_reverse_dns",
-              "source_base_domain", "source_name", "source_type",
-              "delivery_result", "auth_failure", "reported_domain",
-              "authentication_mechanisms", "sample_headers_only"]
+    fields = [
+        "feedback_type",
+        "user_agent",
+        "version",
+        "original_envelope_id",
+        "original_mail_from",
+        "original_rcpt_to",
+        "arrival_date",
+        "arrival_date_utc",
+        "subject",
+        "message_id",
+        "authentication_results",
+        "dkim_domain",
+        "source_ip_address",
+        "source_country",
+        "source_reverse_dns",
+        "source_base_domain",
+        "source_name",
+        "source_type",
+        "delivery_result",
+        "auth_failure",
+        "reported_domain",
+        "authentication_mechanisms",
+        "sample_headers_only",
+    ]
 
     csv_file = StringIO()
     csv_writer = DictWriter(csv_file, fieldnames=fields)
@@ -1131,16 +1268,21 @@ 

Source code for parsedmarc

     return csv_file.getvalue()
-
[docs]def parse_report_email( - input_, - offline=False, - ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - nameservers=None, dns_timeout=2.0, - strip_attachment_payloads=False, - keep_alive=None): + +
+[docs] +def parse_report_email( + input_, + offline=False, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + nameservers=None, + dns_timeout=2.0, + strip_attachment_payloads=False, + keep_alive=None, +): """ Parses a DMARC report from an email @@ -1173,8 +1315,7 @@

Source code for parsedmarc

         msg_headers = json.loads(msg.headers_json)
         date = email.utils.format_datetime(datetime.utcnow())
         if "Date" in msg_headers:
-            date = human_timestamp_to_datetime(
-                msg_headers["Date"])
+            date = human_timestamp_to_datetime(msg_headers["Date"])
         msg = email.message_from_string(input_)
 
     except Exception as e:
@@ -1184,8 +1325,7 @@ 

Source code for parsedmarc

     smtp_tls_report = None
     sample = None
     if "From" in msg_headers:
-        logger.info("Parsing mail from {0} on {1}".format(msg_headers["From"],
-                                                          date))
+        logger.info("Parsing mail from {0} on {1}".format(msg_headers["From"], date))
     if "Subject" in msg_headers:
         subject = msg_headers["Subject"]
     for part in msg.walk():
@@ -1200,8 +1340,7 @@ 

Source code for parsedmarc

                     feedback_report = payload
                 else:
                     feedback_report = b64decode(payload).__str__()
-                feedback_report = feedback_report.lstrip(
-                    "b'").rstrip("'")
+                feedback_report = feedback_report.lstrip("b'").rstrip("'")
                 feedback_report = feedback_report.replace("\\r", "")
                 feedback_report = feedback_report.replace("\\n", "\n")
             except (ValueError, TypeError, binascii.Error):
@@ -1215,13 +1354,15 @@ 

Source code for parsedmarc

             if "{" not in payload:
                 payload = str(b64decode(payload))
             smtp_tls_report = parse_smtp_tls_report_json(payload)
-            return OrderedDict([("report_type", "smtp_tls"),
-                                ("report", smtp_tls_report)])
+            return OrderedDict(
+                [("report_type", "smtp_tls"), ("report", smtp_tls_report)]
+            )
         elif content_type == "application/tlsrpt+gzip":
             payload = extract_report(payload)
             smtp_tls_report = parse_smtp_tls_report_json(payload)
-            return OrderedDict([("report_type", "smtp_tls"),
-                                ("report", smtp_tls_report)])
+            return OrderedDict(
+                [("report_type", "smtp_tls"), ("report", smtp_tls_report)]
+            )
 
         elif content_type == "text/plain":
             if "A message claiming to be from you has failed" in payload:
@@ -1233,13 +1374,13 @@ 

Source code for parsedmarc

                         field_name = match[0].lower().replace(" ", "-")
                         fields[field_name] = match[1].strip()
 
-                    feedback_report = "Arrival-Date: {}\n" \
-                                      "Source-IP: {}" \
-                                      "".format(fields["received-date"],
-                                                fields["sender-ip-address"])
+                    feedback_report = "Arrival-Date: {}\n" "Source-IP: {}" "".format(
+                        fields["received-date"], fields["sender-ip-address"]
+                    )
                 except Exception as e:
-                    error = 'Unable to parse message with ' \
-                            'subject "{0}": {1}'.format(subject, e)
+                    error = "Unable to parse message with " 'subject "{0}": {1}'.format(
+                        subject, e
+                    )
                     raise InvalidDMARCReport(error)
 
                 sample = parts[1].lstrip()
@@ -1247,14 +1388,14 @@ 

Source code for parsedmarc

         else:
             try:
                 payload = b64decode(payload)
-                if payload.startswith(MAGIC_ZIP) or \
-                        payload.startswith(MAGIC_GZIP):
+                if payload.startswith(MAGIC_ZIP) or payload.startswith(MAGIC_GZIP):
                     payload = extract_report(payload)
                     ns = nameservers
                     if payload.startswith("{"):
                         smtp_tls_report = parse_smtp_tls_report_json(payload)
-                        result = OrderedDict([("report_type", "smtp_tls"),
-                                              ("report", smtp_tls_report)])
+                        result = OrderedDict(
+                            [("report_type", "smtp_tls"), ("report", smtp_tls_report)]
+                        )
                         return result
                     aggregate_report = parse_aggregate_report_xml(
                         payload,
@@ -1265,23 +1406,28 @@ 

Source code for parsedmarc

                         offline=offline,
                         nameservers=ns,
                         timeout=dns_timeout,
-                        keep_alive=keep_alive)
-                    result = OrderedDict([("report_type", "aggregate"),
-                                          ("report", aggregate_report)])
+                        keep_alive=keep_alive,
+                    )
+                    result = OrderedDict(
+                        [("report_type", "aggregate"), ("report", aggregate_report)]
+                    )
                     return result
 
             except (TypeError, ValueError, binascii.Error):
                 pass
 
             except InvalidAggregateReport as e:
-                error = 'Message with subject "{0}" ' \
-                        'is not a valid ' \
-                        'aggregate DMARC report: {1}'.format(subject, e)
+                error = (
+                    'Message with subject "{0}" '
+                    "is not a valid "
+                    "aggregate DMARC report: {1}".format(subject, e)
+                )
                 raise ParserError(error)
 
             except Exception as e:
-                error = 'Unable to parse message with ' \
-                        'subject "{0}": {1}'.format(subject, e)
+                error = "Unable to parse message with " 'subject "{0}": {1}'.format(
+                    subject, e
+                )
                 raise ParserError(error)
 
     if feedback_report and sample:
@@ -1297,31 +1443,41 @@ 

Source code for parsedmarc

                 reverse_dns_map_url=reverse_dns_map_url,
                 nameservers=nameservers,
                 dns_timeout=dns_timeout,
-                strip_attachment_payloads=strip_attachment_payloads)
+                strip_attachment_payloads=strip_attachment_payloads,
+            )
         except InvalidForensicReport as e:
-            error = 'Message with subject "{0}" ' \
-                    'is not a valid ' \
-                    'forensic DMARC report: {1}'.format(subject, e)
+            error = (
+                'Message with subject "{0}" '
+                "is not a valid "
+                "forensic DMARC report: {1}".format(subject, e)
+            )
             raise InvalidForensicReport(error)
         except Exception as e:
             raise InvalidForensicReport(e.__str__())
 
-        result = OrderedDict([("report_type", "forensic"),
-                              ("report", forensic_report)])
+        result = OrderedDict([("report_type", "forensic"), ("report", forensic_report)])
         return result
 
     if result is None:
-        error = 'Message with subject "{0}" is ' \
-                'not a valid report'.format(subject)
+        error = 'Message with subject "{0}" is ' "not a valid report".format(subject)
         raise InvalidDMARCReport(error)
-
[docs]def parse_report_file(input_, nameservers=None, dns_timeout=2.0, - strip_attachment_payloads=False, ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, keep_alive=None): + +
+[docs] +def parse_report_file( + input_, + nameservers=None, + dns_timeout=2.0, + strip_attachment_payloads=False, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + keep_alive=None, +): """Parses a DMARC aggregate or forensic file at the given path, a file-like object. or bytes @@ -1362,14 +1518,13 @@

Source code for parsedmarc

             offline=offline,
             nameservers=nameservers,
             dns_timeout=dns_timeout,
-            keep_alive=keep_alive)
-        results = OrderedDict([("report_type", "aggregate"),
-                               ("report", report)])
+            keep_alive=keep_alive,
+        )
+        results = OrderedDict([("report_type", "aggregate"), ("report", report)])
     except InvalidAggregateReport:
         try:
             report = parse_smtp_tls_report_json(content)
-            results = OrderedDict([("report_type", "smtp_tls"),
-                                   ("report", report)])
+            results = OrderedDict([("report_type", "smtp_tls"), ("report", report)])
         except InvalidSMTPTLSReport:
             try:
                 sa = strip_attachment_payloads
@@ -1383,19 +1538,27 @@ 

Source code for parsedmarc

                     nameservers=nameservers,
                     dns_timeout=dns_timeout,
                     strip_attachment_payloads=sa,
-                    keep_alive=keep_alive)
+                    keep_alive=keep_alive,
+                )
             except InvalidDMARCReport:
                 raise ParserError("Not a valid report")
     return results
-
[docs]def get_dmarc_reports_from_mbox(input_, nameservers=None, dns_timeout=2.0, - strip_attachment_payloads=False, - ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False): + +
+[docs] +def get_dmarc_reports_from_mbox( + input_, + nameservers=None, + dns_timeout=2.0, + strip_attachment_payloads=False, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, +): """Parses a mailbox in mbox format containing e-mails with attached DMARC reports @@ -1423,13 +1586,10 @@

Source code for parsedmarc

         mbox = mailbox.mbox(input_)
         message_keys = mbox.keys()
         total_messages = len(message_keys)
-        logger.debug("Found {0} messages in {1}".format(total_messages,
-                                                        input_))
+        logger.debug("Found {0} messages in {1}".format(total_messages, input_))
         for i in range(len(message_keys)):
             message_key = message_keys[i]
-            logger.info("Processing message {0} of {1}".format(
-                i+1, total_messages
-            ))
+            logger.info("Processing message {0} of {1}".format(i + 1, total_messages))
             msg_content = mbox.get_string(message_key)
             try:
                 sa = strip_attachment_payloads
@@ -1442,7 +1602,8 @@ 

Source code for parsedmarc

                     offline=offline,
                     nameservers=nameservers,
                     dns_timeout=dns_timeout,
-                    strip_attachment_payloads=sa)
+                    strip_attachment_payloads=sa,
+                )
                 if parsed_email["report_type"] == "aggregate":
                     aggregate_reports.append(parsed_email["report"])
                 elif parsed_email["report_type"] == "forensic":
@@ -1453,27 +1614,36 @@ 

Source code for parsedmarc

                 logger.warning(error.__str__())
     except mailbox.NoSuchMailboxError:
         raise InvalidDMARCReport("Mailbox {0} does not exist".format(input_))
-    return OrderedDict([("aggregate_reports", aggregate_reports),
-                        ("forensic_reports", forensic_reports),
-                        ("smtp_tls_reports", smtp_tls_reports)])
- - -
[docs]def get_dmarc_reports_from_mailbox(connection: MailboxConnection, - reports_folder="INBOX", - archive_folder="Archive", - delete=False, - test=False, - ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, - nameservers=None, - dns_timeout=6.0, - strip_attachment_payloads=False, - results=None, - batch_size=10, - create_folders=True): + return OrderedDict( + [ + ("aggregate_reports", aggregate_reports), + ("forensic_reports", forensic_reports), + ("smtp_tls_reports", smtp_tls_reports), + ] + )
+ + + +
+[docs] +def get_dmarc_reports_from_mailbox( + connection: MailboxConnection, + reports_folder="INBOX", + archive_folder="Archive", + delete=False, + test=False, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + nameservers=None, + dns_timeout=6.0, + strip_attachment_payloads=False, + results=None, + batch_size=10, + create_folders=True, +): """ Fetches and parses DMARC reports from a mailbox @@ -1513,15 +1683,10 @@

Source code for parsedmarc

     aggregate_report_msg_uids = []
     forensic_report_msg_uids = []
     smtp_tls_msg_uids = []
-    folder_separator = connection.get_folder_separator()
-    aggregate_reports_folder = "{0}{1}Aggregate".format(archive_folder,
-                                                        folder_separator)
-    forensic_reports_folder = "{0}{1}Forensic".format(archive_folder,
-                                                      folder_separator)
-    smtp_tls_reports_folder = "{0}{1}SMTP-TLS".format(archive_folder,
-                                                      folder_separator)
-    invalid_reports_folder = "{0}{1}Invalid".format(archive_folder,
-                                                    folder_separator)
+    aggregate_reports_folder = "{0}/Aggregate".format(archive_folder)
+    forensic_reports_folder = "{0}/Forensic".format(archive_folder)
+    smtp_tls_reports_folder = "{0}/SMTP-TLS".format(archive_folder)
+    invalid_reports_folder = "{0}/Invalid".format(archive_folder)
 
     if results:
         aggregate_reports = results["aggregate_reports"].copy()
@@ -1537,8 +1702,7 @@ 

Source code for parsedmarc

 
     messages = connection.fetch_messages(reports_folder, batch_size=batch_size)
     total_messages = len(messages)
-    logger.debug("Found {0} messages in {1}".format(len(messages),
-                                                    reports_folder))
+    logger.debug("Found {0} messages in {1}".format(len(messages), reports_folder))
 
     if batch_size:
         message_limit = min(total_messages, batch_size)
@@ -1549,9 +1713,11 @@ 

Source code for parsedmarc

 
     for i in range(message_limit):
         msg_uid = messages[i]
-        logger.debug("Processing message {0} of {1}: UID {2}".format(
-            i+1, message_limit, msg_uid
-        ))
+        logger.debug(
+            "Processing message {0} of {1}: UID {2}".format(
+                i + 1, message_limit, msg_uid
+            )
+        )
         msg_content = connection.fetch_message(msg_uid)
         try:
             sa = strip_attachment_payloads
@@ -1565,7 +1731,8 @@ 

Source code for parsedmarc

                 reverse_dns_map_url=reverse_dns_map_url,
                 offline=offline,
                 strip_attachment_payloads=sa,
-                keep_alive=connection.keepalive)
+                keep_alive=connection.keepalive,
+            )
             if parsed_email["report_type"] == "aggregate":
                 aggregate_reports.append(parsed_email["report"])
                 aggregate_report_msg_uids.append(msg_uid)
@@ -1579,27 +1746,30 @@ 

Source code for parsedmarc

             logger.warning(error.__str__())
             if not test:
                 if delete:
-                    logger.debug(
-                        "Deleting message UID {0}".format(msg_uid))
+                    logger.debug("Deleting message UID {0}".format(msg_uid))
                     connection.delete_message(msg_uid)
                 else:
                     logger.debug(
                         "Moving message UID {0} to {1}".format(
-                            msg_uid, invalid_reports_folder))
+                            msg_uid, invalid_reports_folder
+                        )
+                    )
                     connection.move_message(msg_uid, invalid_reports_folder)
 
     if not test:
         if delete:
-            processed_messages = aggregate_report_msg_uids + \
-                                 forensic_report_msg_uids + \
-                                 smtp_tls_msg_uids
+            processed_messages = (
+                aggregate_report_msg_uids + forensic_report_msg_uids + smtp_tls_msg_uids
+            )
 
             number_of_processed_msgs = len(processed_messages)
             for i in range(number_of_processed_msgs):
                 msg_uid = processed_messages[i]
                 logger.debug(
                     "Deleting message {0} of {1}: UID {2}".format(
-                        i + 1, number_of_processed_msgs, msg_uid))
+                        i + 1, number_of_processed_msgs, msg_uid
+                    )
+                )
                 try:
                     connection.delete_message(msg_uid)
 
@@ -1612,17 +1782,19 @@ 

Source code for parsedmarc

                 log_message = "Moving aggregate report messages from"
                 logger.debug(
                     "{0} {1} to {2}".format(
-                        log_message, reports_folder,
-                        aggregate_reports_folder))
+                        log_message, reports_folder, aggregate_reports_folder
+                    )
+                )
                 number_of_agg_report_msgs = len(aggregate_report_msg_uids)
                 for i in range(number_of_agg_report_msgs):
                     msg_uid = aggregate_report_msg_uids[i]
                     logger.debug(
                         "Moving message {0} of {1}: UID {2}".format(
-                            i+1, number_of_agg_report_msgs, msg_uid))
+                            i + 1, number_of_agg_report_msgs, msg_uid
+                        )
+                    )
                     try:
-                        connection.move_message(msg_uid,
-                                                aggregate_reports_folder)
+                        connection.move_message(msg_uid, aggregate_reports_folder)
                     except Exception as e:
                         message = "Error moving message UID"
                         e = "{0} {1}: {2}".format(message, msg_uid, e)
@@ -1630,46 +1802,52 @@ 

Source code for parsedmarc

             if len(forensic_report_msg_uids) > 0:
                 message = "Moving forensic report messages from"
                 logger.debug(
-                    "{0} {1} to {2}".format(message,
-                                            reports_folder,
-                                            forensic_reports_folder))
+                    "{0} {1} to {2}".format(
+                        message, reports_folder, forensic_reports_folder
+                    )
+                )
                 number_of_forensic_msgs = len(forensic_report_msg_uids)
                 for i in range(number_of_forensic_msgs):
                     msg_uid = forensic_report_msg_uids[i]
                     message = "Moving message"
-                    logger.debug("{0} {1} of {2}: UID {3}".format(
-                        message,
-                        i + 1, number_of_forensic_msgs, msg_uid))
+                    logger.debug(
+                        "{0} {1} of {2}: UID {3}".format(
+                            message, i + 1, number_of_forensic_msgs, msg_uid
+                        )
+                    )
                     try:
-                        connection.move_message(msg_uid,
-                                                forensic_reports_folder)
+                        connection.move_message(msg_uid, forensic_reports_folder)
                     except Exception as e:
-                        e = "Error moving message UID {0}: {1}".format(
-                            msg_uid, e)
+                        e = "Error moving message UID {0}: {1}".format(msg_uid, e)
                         logger.error("Mailbox error: {0}".format(e))
             if len(smtp_tls_msg_uids) > 0:
                 message = "Moving SMTP TLS report messages from"
                 logger.debug(
-                    "{0} {1} to {2}".format(message,
-                                            reports_folder,
-                                            smtp_tls_reports_folder))
+                    "{0} {1} to {2}".format(
+                        message, reports_folder, smtp_tls_reports_folder
+                    )
+                )
                 number_of_smtp_tls_uids = len(smtp_tls_msg_uids)
                 for i in range(number_of_smtp_tls_uids):
                     msg_uid = smtp_tls_msg_uids[i]
                     message = "Moving message"
-                    logger.debug("{0} {1} of {2}: UID {3}".format(
-                        message,
-                        i + 1, number_of_smtp_tls_uids, msg_uid))
+                    logger.debug(
+                        "{0} {1} of {2}: UID {3}".format(
+                            message, i + 1, number_of_smtp_tls_uids, msg_uid
+                        )
+                    )
                     try:
-                        connection.move_message(msg_uid,
-                                                smtp_tls_reports_folder)
+                        connection.move_message(msg_uid, smtp_tls_reports_folder)
                     except Exception as e:
-                        e = "Error moving message UID {0}: {1}".format(
-                            msg_uid, e)
+                        e = "Error moving message UID {0}: {1}".format(msg_uid, e)
                         logger.error("Mailbox error: {0}".format(e))
-    results = OrderedDict([("aggregate_reports", aggregate_reports),
-                           ("forensic_reports", forensic_reports),
-                           ("smtp_tls_reports", smtp_tls_reports)])
+    results = OrderedDict(
+        [
+            ("aggregate_reports", aggregate_reports),
+            ("forensic_reports", forensic_reports),
+            ("smtp_tls_reports", smtp_tls_reports),
+        ]
+    )
 
     total_messages = len(connection.fetch_messages(reports_folder))
 
@@ -1689,23 +1867,33 @@ 

Source code for parsedmarc

             always_use_local_files=always_use_local_files,
             reverse_dns_map_path=reverse_dns_map_path,
             reverse_dns_map_url=reverse_dns_map_url,
-            offline=offline
+            offline=offline,
         )
 
     return results
-
[docs]def watch_inbox(mailbox_connection: MailboxConnection, - callback: Callable, - reports_folder="INBOX", - archive_folder="Archive", delete=False, test=False, - check_timeout=30, ip_db_path=None, - always_use_local_files=False, - reverse_dns_map_path=None, - reverse_dns_map_url=None, - offline=False, nameservers=None, - dns_timeout=6.0, strip_attachment_payloads=False, - batch_size=None): + +
+[docs] +def watch_inbox( + mailbox_connection: MailboxConnection, + callback: Callable, + reports_folder="INBOX", + archive_folder="Archive", + delete=False, + test=False, + check_timeout=30, + ip_db_path=None, + always_use_local_files=False, + reverse_dns_map_path=None, + reverse_dns_map_url=None, + offline=False, + nameservers=None, + dns_timeout=6.0, + strip_attachment_payloads=False, + batch_size=None, +): """ Watches the mailbox for new messages and sends the results to a callback function @@ -1749,11 +1937,12 @@

Source code for parsedmarc

             dns_timeout=dns_timeout,
             strip_attachment_payloads=sa,
             batch_size=batch_size,
-            create_folders=False)
+            create_folders=False,
+        )
         callback(res)
 
-    mailbox_connection.watch(check_callback=check_callback,
-                             check_timeout=check_timeout)
+ mailbox_connection.watch(check_callback=check_callback, check_timeout=check_timeout)
+ def append_json(filename, reports): @@ -1791,13 +1980,18 @@

Source code for parsedmarc

         output.write(csv)
 
 
-
[docs]def save_output(results, output_directory="output", - aggregate_json_filename="aggregate.json", - forensic_json_filename="forensic.json", - smtp_tls_json_filename="smtp_tls.json", - aggregate_csv_filename="aggregate.csv", - forensic_csv_filename="forensic.csv", - smtp_tls_csv_filename="smtp_tls.csv"): +
+[docs] +def save_output( + results, + output_directory="output", + aggregate_json_filename="aggregate.json", + forensic_json_filename="forensic.json", + smtp_tls_json_filename="smtp_tls.json", + aggregate_csv_filename="aggregate.csv", + forensic_csv_filename="forensic.csv", + smtp_tls_csv_filename="smtp_tls.csv", +): """ Save report data in the given directory @@ -1823,23 +2017,32 @@

Source code for parsedmarc

     else:
         os.makedirs(output_directory)
 
-    append_json(os.path.join(output_directory, aggregate_json_filename),
-                aggregate_reports)
+    append_json(
+        os.path.join(output_directory, aggregate_json_filename), aggregate_reports
+    )
 
-    append_csv(os.path.join(output_directory, aggregate_csv_filename),
-               parsed_aggregate_reports_to_csv(aggregate_reports))
+    append_csv(
+        os.path.join(output_directory, aggregate_csv_filename),
+        parsed_aggregate_reports_to_csv(aggregate_reports),
+    )
 
-    append_json(os.path.join(output_directory, forensic_json_filename),
-                forensic_reports)
+    append_json(
+        os.path.join(output_directory, forensic_json_filename), forensic_reports
+    )
 
-    append_csv(os.path.join(output_directory, forensic_csv_filename),
-               parsed_forensic_reports_to_csv(forensic_reports))
+    append_csv(
+        os.path.join(output_directory, forensic_csv_filename),
+        parsed_forensic_reports_to_csv(forensic_reports),
+    )
 
-    append_json(os.path.join(output_directory, smtp_tls_json_filename),
-                smtp_tls_reports)
+    append_json(
+        os.path.join(output_directory, smtp_tls_json_filename), smtp_tls_reports
+    )
 
-    append_csv(os.path.join(output_directory, smtp_tls_csv_filename),
-               parsed_smtp_tls_reports_to_csv(smtp_tls_reports))
+    append_csv(
+        os.path.join(output_directory, smtp_tls_csv_filename),
+        parsed_smtp_tls_reports_to_csv(smtp_tls_reports),
+    )
 
     samples_directory = os.path.join(output_directory, "samples")
     if not os.path.exists(samples_directory):
@@ -1865,7 +2068,10 @@ 

Source code for parsedmarc

             sample_file.write(sample)
-
[docs]def get_report_zip(results): + +
+[docs] +def get_report_zip(results): """ Creates a zip file of parsed report output @@ -1875,6 +2081,7 @@

Source code for parsedmarc

     Returns:
         bytes: zip file bytes
     """
+
     def add_subdir(root_path, subdir):
         subdir_path = os.path.join(root_path, subdir)
         for subdir_root, subdir_dirs, subdir_files in os.walk(subdir_path):
@@ -1891,13 +2098,12 @@ 

Source code for parsedmarc

     tmp_dir = tempfile.mkdtemp()
     try:
         save_output(results, tmp_dir)
-        with zipfile.ZipFile(storage, 'w', zipfile.ZIP_DEFLATED) as zip_file:
+        with zipfile.ZipFile(storage, "w", zipfile.ZIP_DEFLATED) as zip_file:
             for root, dirs, files in os.walk(tmp_dir):
                 for file in files:
                     file_path = os.path.join(root, file)
                     if os.path.isfile(file_path):
-                        arcname = os.path.join(os.path.relpath(root, tmp_dir),
-                                               file)
+                        arcname = os.path.join(os.path.relpath(root, tmp_dir), file)
                         zip_file.write(file_path, arcname)
                 for directory in dirs:
                     dir_path = os.path.join(root, directory)
@@ -1910,11 +2116,25 @@ 

Source code for parsedmarc

     return storage.getvalue()
-
[docs]def email_results(results, host, mail_from, mail_to, - mail_cc=None, mail_bcc=None, port=0, - require_encryption=False, verify=True, - username=None, password=None, subject=None, - attachment_filename=None, message=None): + +
+[docs] +def email_results( + results, + host, + mail_from, + mail_to, + mail_cc=None, + mail_bcc=None, + port=0, + require_encryption=False, + verify=True, + username=None, + password=None, + subject=None, + attachment_filename=None, + message=None, +): """ Emails parsing results as a zip file @@ -1952,11 +2172,22 @@

Source code for parsedmarc

     zip_bytes = get_report_zip(results)
     attachments = [(filename, zip_bytes)]
 
-    send_email(host, mail_from, mail_to, message_cc=mail_cc,
-               message_bcc=mail_bcc, port=port,
-               require_encryption=require_encryption, verify=verify,
-               username=username, password=password, subject=subject,
-               attachments=attachments, plain_message=message)
+ send_email( + host, + mail_from, + mail_to, + message_cc=mail_cc, + message_bcc=mail_bcc, + port=port, + require_encryption=require_encryption, + verify=verify, + username=username, + password=password, + subject=subject, + attachments=attachments, + plain_message=message, + )
+
diff --git a/_modules/parsedmarc/elastic.html b/_modules/parsedmarc/elastic.html index 6cba6345..b64a5eea 100644 --- a/_modules/parsedmarc/elastic.html +++ b/_modules/parsedmarc/elastic.html @@ -1,23 +1,20 @@ + + - + - parsedmarc.elastic — parsedmarc 8.15.0 documentation - - + parsedmarc.elastic — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -91,8 +85,20 @@

Source code for parsedmarc.elastic

 from collections import OrderedDict
 
 from elasticsearch_dsl.search import Q
-from elasticsearch_dsl import connections, Object, Document, Index, Nested, \
-    InnerDoc, Integer, Text, Boolean, Ip, Date, Search
+from elasticsearch_dsl import (
+    connections,
+    Object,
+    Document,
+    Index,
+    Nested,
+    InnerDoc,
+    Integer,
+    Text,
+    Boolean,
+    Ip,
+    Date,
+    Search,
+)
 from elasticsearch.helpers import reindex
 
 from parsedmarc.log import logger
@@ -100,10 +106,13 @@ 

Source code for parsedmarc.elastic

 from parsedmarc import InvalidForensicReport
 
 
-
[docs]class ElasticsearchError(Exception): +
+[docs] +class ElasticsearchError(Exception): """Raised when an Elasticsearch error occurs"""
+ class _PolicyOverride(InnerDoc): type = Text() comment = Text() @@ -164,24 +173,21 @@

Source code for parsedmarc.elastic

     spf_results = Nested(_SPFResult)
 
     def add_policy_override(self, type_, comment):
-        self.policy_overrides.append(_PolicyOverride(type=type_,
-                                                     comment=comment))
+        self.policy_overrides.append(_PolicyOverride(type=type_, comment=comment))
 
     def add_dkim_result(self, domain, selector, result):
-        self.dkim_results.append(_DKIMResult(domain=domain,
-                                             selector=selector,
-                                             result=result))
+        self.dkim_results.append(
+            _DKIMResult(domain=domain, selector=selector, result=result)
+        )
 
     def add_spf_result(self, domain, scope, result):
-        self.spf_results.append(_SPFResult(domain=domain,
-                                           scope=scope,
-                                           result=result))
+        self.spf_results.append(_SPFResult(domain=domain, scope=scope, result=result))
 
-    def save(self, ** kwargs):
+    def save(self, **kwargs):
         self.passed_dmarc = False
         self.passed_dmarc = self.spf_aligned or self.dkim_aligned
 
-        return super().save(** kwargs)
+        return super().save(**kwargs)
 
 
 class _EmailAddressDoc(InnerDoc):
@@ -211,24 +217,25 @@ 

Source code for parsedmarc.elastic

     attachments = Nested(_EmailAttachmentDoc)
 
     def add_to(self, display_name, address):
-        self.to.append(_EmailAddressDoc(display_name=display_name,
-                                        address=address))
+        self.to.append(_EmailAddressDoc(display_name=display_name, address=address))
 
     def add_reply_to(self, display_name, address):
-        self.reply_to.append(_EmailAddressDoc(display_name=display_name,
-                                              address=address))
+        self.reply_to.append(
+            _EmailAddressDoc(display_name=display_name, address=address)
+        )
 
     def add_cc(self, display_name, address):
-        self.cc.append(_EmailAddressDoc(display_name=display_name,
-                                        address=address))
+        self.cc.append(_EmailAddressDoc(display_name=display_name, address=address))
 
     def add_bcc(self, display_name, address):
-        self.bcc.append(_EmailAddressDoc(display_name=display_name,
-                                         address=address))
+        self.bcc.append(_EmailAddressDoc(display_name=display_name, address=address))
 
     def add_attachment(self, filename, content_type, sha256):
-        self.attachments.append(_EmailAttachmentDoc(filename=filename,
-                                content_type=content_type, sha256=sha256))
+        self.attachments.append(
+            _EmailAttachmentDoc(
+                filename=filename, content_type=content_type, sha256=sha256
+            )
+        )
 
 
 class _ForensicReportDoc(Document):
@@ -273,14 +280,18 @@ 

Source code for parsedmarc.elastic

     failed_session_count = Integer()
     failure_details = Nested(_SMTPTLSFailureDetailsDoc)
 
-    def add_failure_details(self, result_type, ip_address,
-                            receiving_ip,
-                            receiving_mx_helo,
-                            failed_session_count,
-                            sending_mta_ip=None,
-                            receiving_mx_hostname=None,
-                            additional_information_uri=None,
-                            failure_reason_code=None):
+    def add_failure_details(
+        self,
+        result_type,
+        ip_address,
+        receiving_ip,
+        receiving_mx_helo,
+        failed_session_count,
+        sending_mta_ip=None,
+        receiving_mx_hostname=None,
+        additional_information_uri=None,
+        failure_reason_code=None,
+    ):
         _details = _SMTPTLSFailureDetailsDoc(
             result_type=result_type,
             ip_address=ip_address,
@@ -290,13 +301,12 @@ 

Source code for parsedmarc.elastic

             receiving_ip=receiving_ip,
             failed_session_count=failed_session_count,
             additional_information=additional_information_uri,
-            failure_reason_code=failure_reason_code
+            failure_reason_code=failure_reason_code,
         )
         self.failure_details.append(_details)
 
 
 class _SMTPTLSReportDoc(Document):
-
     class Index:
         name = "smtp_tls"
 
@@ -308,27 +318,45 @@ 

Source code for parsedmarc.elastic

     report_id = Text()
     policies = Nested(_SMTPTLSPolicyDoc)
 
-    def add_policy(self, policy_type, policy_domain,
-                   successful_session_count,
-                   failed_session_count,
-                   policy_string=None,
-                   mx_host_patterns=None,
-                   failure_details=None):
-        self.policies.append(policy_type=policy_type,
-                             policy_domain=policy_domain,
-                             successful_session_count=successful_session_count,
-                             failed_session_count=failed_session_count,
-                             policy_string=policy_string,
-                             mx_host_patterns=mx_host_patterns,
-                             failure_details=failure_details)
-
-
-
[docs]class AlreadySaved(ValueError): + def add_policy( + self, + policy_type, + policy_domain, + successful_session_count, + failed_session_count, + policy_string=None, + mx_host_patterns=None, + failure_details=None, + ): + self.policies.append( + policy_type=policy_type, + policy_domain=policy_domain, + successful_session_count=successful_session_count, + failed_session_count=failed_session_count, + policy_string=policy_string, + mx_host_patterns=mx_host_patterns, + failure_details=failure_details, + ) + + +
+[docs] +class AlreadySaved(ValueError): """Raised when a report to be saved matches an existing report"""
-
[docs]def set_hosts(hosts, use_ssl=False, ssl_cert_path=None, - username=None, password=None, apiKey=None, timeout=60.0): + +
+[docs] +def set_hosts( + hosts, + use_ssl=False, + ssl_cert_path=None, + username=None, + password=None, + apiKey=None, + timeout=60.0, +): """ Sets the Elasticsearch hosts to use @@ -343,25 +371,25 @@

Source code for parsedmarc.elastic

     """
     if not isinstance(hosts, list):
         hosts = [hosts]
-    conn_params = {
-        "hosts": hosts,
-        "timeout": timeout
-    }
+    conn_params = {"hosts": hosts, "timeout": timeout}
     if use_ssl:
-        conn_params['use_ssl'] = True
+        conn_params["use_ssl"] = True
         if ssl_cert_path:
-            conn_params['verify_certs'] = True
-            conn_params['ca_certs'] = ssl_cert_path
+            conn_params["verify_certs"] = True
+            conn_params["ca_certs"] = ssl_cert_path
         else:
-            conn_params['verify_certs'] = False
+            conn_params["verify_certs"] = False
     if username:
-        conn_params['http_auth'] = (username+":"+password)
+        conn_params["http_auth"] = username + ":" + password
     if apiKey:
-        conn_params['api_key'] = apiKey
+        conn_params["api_key"] = apiKey
     connections.create_connection(**conn_params)
-
[docs]def create_indexes(names, settings=None): + +
+[docs] +def create_indexes(names, settings=None): """ Create Elasticsearch indexes @@ -376,17 +404,18 @@

Source code for parsedmarc.elastic

             if not index.exists():
                 logger.debug("Creating Elasticsearch index: {0}".format(name))
                 if settings is None:
-                    index.settings(number_of_shards=1,
-                                   number_of_replicas=0)
+                    index.settings(number_of_shards=1, number_of_replicas=0)
                 else:
                     index.settings(**settings)
                 index.create()
         except Exception as e:
-            raise ElasticsearchError(
-                "Elasticsearch error: {0}".format(e.__str__()))
+ raise ElasticsearchError("Elasticsearch error: {0}".format(e.__str__()))
-
[docs]def migrate_indexes(aggregate_indexes=None, forensic_indexes=None): + +
+[docs] +def migrate_indexes(aggregate_indexes=None, forensic_indexes=None): """ Updates index mappings @@ -415,33 +444,34 @@

Source code for parsedmarc.elastic

         fo_type = fo_mapping["type"]
         if fo_type == "long":
             new_index_name = "{0}-v{1}".format(aggregate_index_name, version)
-            body = {"properties": {"published_policy.fo": {
-                "type": "text",
-                "fields": {
-                    "keyword": {
-                        "type": "keyword",
-                        "ignore_above": 256
+            body = {
+                "properties": {
+                    "published_policy.fo": {
+                        "type": "text",
+                        "fields": {"keyword": {"type": "keyword", "ignore_above": 256}},
                     }
                 }
             }
-            }
-            }
             Index(new_index_name).create()
             Index(new_index_name).put_mapping(doc_type=doc, body=body)
-            reindex(connections.get_connection(), aggregate_index_name,
-                    new_index_name)
+            reindex(connections.get_connection(), aggregate_index_name, new_index_name)
             Index(aggregate_index_name).delete()
 
     for forensic_index in forensic_indexes:
         pass
-
[docs]def save_aggregate_report_to_elasticsearch(aggregate_report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_aggregate_report_to_elasticsearch( + aggregate_report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ Saves a parsed DMARC aggregate report to Elasticsearch @@ -462,10 +492,8 @@

Source code for parsedmarc.elastic

     org_name = metadata["org_name"]
     report_id = metadata["report_id"]
     domain = aggregate_report["policy_published"]["domain"]
-    begin_date = human_timestamp_to_datetime(metadata["begin_date"],
-                                             to_utc=True)
-    end_date = human_timestamp_to_datetime(metadata["end_date"],
-                                           to_utc=True)
+    begin_date = human_timestamp_to_datetime(metadata["begin_date"], to_utc=True)
+    end_date = human_timestamp_to_datetime(metadata["end_date"], to_utc=True)
     begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ")
     end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ")
     if monthly_indexes:
@@ -474,8 +502,7 @@ 

Source code for parsedmarc.elastic

         index_date = begin_date.strftime("%Y-%m-%d")
     aggregate_report["begin_date"] = begin_date
     aggregate_report["end_date"] = end_date
-    date_range = [aggregate_report["begin_date"],
-                  aggregate_report["end_date"]]
+    date_range = [aggregate_report["begin_date"], aggregate_report["end_date"]]
 
     org_name_query = Q(dict(match_phrase=dict(org_name=org_name)))
     report_id_query = Q(dict(match_phrase=dict(report_id=report_id)))
@@ -497,18 +524,20 @@ 

Source code for parsedmarc.elastic

     try:
         existing = search.execute()
     except Exception as error_:
-        raise ElasticsearchError("Elasticsearch's search for existing report \
-            error: {}".format(error_.__str__()))
+        raise ElasticsearchError(
+            "Elasticsearch's search for existing report \
+            error: {}".format(error_.__str__())
+        )
 
     if len(existing) > 0:
-        raise AlreadySaved("An aggregate report ID {0} from {1} about {2} "
-                           "with a date range of {3} UTC to {4} UTC already "
-                           "exists in "
-                           "Elasticsearch".format(report_id,
-                                                  org_name,
-                                                  domain,
-                                                  begin_date_human,
-                                                  end_date_human))
+        raise AlreadySaved(
+            "An aggregate report ID {0} from {1} about {2} "
+            "with a date range of {3} UTC to {4} UTC already "
+            "exists in "
+            "Elasticsearch".format(
+                report_id, org_name, domain, begin_date_human, end_date_human
+            )
+        )
     published_policy = _PublishedPolicy(
         domain=aggregate_report["policy_published"]["domain"],
         adkim=aggregate_report["policy_published"]["adkim"],
@@ -516,7 +545,7 @@ 

Source code for parsedmarc.elastic

         p=aggregate_report["policy_published"]["p"],
         sp=aggregate_report["policy_published"]["sp"],
         pct=aggregate_report["policy_published"]["pct"],
-        fo=aggregate_report["policy_published"]["fo"]
+        fo=aggregate_report["policy_published"]["fo"],
     )
 
     for record in aggregate_report["records"]:
@@ -539,28 +568,33 @@ 

Source code for parsedmarc.elastic

             source_name=record["source"]["name"],
             message_count=record["count"],
             disposition=record["policy_evaluated"]["disposition"],
-            dkim_aligned=record["policy_evaluated"]["dkim"] is not None and
-            record["policy_evaluated"]["dkim"].lower() == "pass",
-            spf_aligned=record["policy_evaluated"]["spf"] is not None and
-            record["policy_evaluated"]["spf"].lower() == "pass",
+            dkim_aligned=record["policy_evaluated"]["dkim"] is not None
+            and record["policy_evaluated"]["dkim"].lower() == "pass",
+            spf_aligned=record["policy_evaluated"]["spf"] is not None
+            and record["policy_evaluated"]["spf"].lower() == "pass",
             header_from=record["identifiers"]["header_from"],
             envelope_from=record["identifiers"]["envelope_from"],
-            envelope_to=record["identifiers"]["envelope_to"]
+            envelope_to=record["identifiers"]["envelope_to"],
         )
 
         for override in record["policy_evaluated"]["policy_override_reasons"]:
-            agg_doc.add_policy_override(type_=override["type"],
-                                        comment=override["comment"])
+            agg_doc.add_policy_override(
+                type_=override["type"], comment=override["comment"]
+            )
 
         for dkim_result in record["auth_results"]["dkim"]:
-            agg_doc.add_dkim_result(domain=dkim_result["domain"],
-                                    selector=dkim_result["selector"],
-                                    result=dkim_result["result"])
+            agg_doc.add_dkim_result(
+                domain=dkim_result["domain"],
+                selector=dkim_result["selector"],
+                result=dkim_result["result"],
+            )
 
         for spf_result in record["auth_results"]["spf"]:
-            agg_doc.add_spf_result(domain=spf_result["domain"],
-                                   scope=spf_result["scope"],
-                                   result=spf_result["result"])
+            agg_doc.add_spf_result(
+                domain=spf_result["domain"],
+                scope=spf_result["scope"],
+                result=spf_result["result"],
+            )
 
         index = "dmarc_aggregate"
         if index_suffix:
@@ -569,41 +603,46 @@ 

Source code for parsedmarc.elastic

             index = "{0}{1}".format(index_prefix, index)
 
         index = "{0}-{1}".format(index, index_date)
-        index_settings = dict(number_of_shards=number_of_shards,
-                              number_of_replicas=number_of_replicas)
+        index_settings = dict(
+            number_of_shards=number_of_shards, number_of_replicas=number_of_replicas
+        )
         create_indexes([index], index_settings)
         agg_doc.meta.index = index
 
         try:
             agg_doc.save()
         except Exception as e:
-            raise ElasticsearchError(
-                "Elasticsearch error: {0}".format(e.__str__()))
+ raise ElasticsearchError("Elasticsearch error: {0}".format(e.__str__()))
-
[docs]def save_forensic_report_to_elasticsearch(forensic_report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_forensic_report_to_elasticsearch( + forensic_report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ - Saves a parsed DMARC forensic report to Elasticsearch - - Args: - forensic_report (OrderedDict): A parsed forensic report - index_suffix (str): The suffix of the name of the index to save to - index_prefix (str): The prefix of the name of the index to save to - monthly_indexes (bool): Use monthly indexes instead of daily - indexes - number_of_shards (int): The number of shards to use in the index - number_of_replicas (int): The number of replicas to use in the - index - - Raises: - AlreadySaved + Saves a parsed DMARC forensic report to Elasticsearch - """ + Args: + forensic_report (OrderedDict): A parsed forensic report + index_suffix (str): The suffix of the name of the index to save to + index_prefix (str): The prefix of the name of the index to save to + monthly_indexes (bool): Use monthly indexes instead of daily + indexes + number_of_shards (int): The number of shards to use in the index + number_of_replicas (int): The number of replicas to use in the + index + + Raises: + AlreadySaved + + """ logger.info("Saving forensic report to Elasticsearch") forensic_report = forensic_report.copy() sample_date = None @@ -648,14 +687,12 @@

Source code for parsedmarc.elastic

     existing = search.execute()
 
     if len(existing) > 0:
-        raise AlreadySaved("A forensic sample to {0} from {1} "
-                           "with a subject of {2} and arrival date of {3} "
-                           "already exists in "
-                           "Elasticsearch".format(to_,
-                                                  from_,
-                                                  subject,
-                                                  arrival_date_human
-                                                  ))
+        raise AlreadySaved(
+            "A forensic sample to {0} from {1} "
+            "with a subject of {2} and arrival date of {3} "
+            "already exists in "
+            "Elasticsearch".format(to_, from_, subject, arrival_date_human)
+        )
 
     parsed_sample = forensic_report["parsed_sample"]
     sample = _ForensicSampleDoc(
@@ -665,25 +702,25 @@ 

Source code for parsedmarc.elastic

         date=sample_date,
         subject=forensic_report["parsed_sample"]["subject"],
         filename_safe_subject=parsed_sample["filename_safe_subject"],
-        body=forensic_report["parsed_sample"]["body"]
+        body=forensic_report["parsed_sample"]["body"],
     )
 
     for address in forensic_report["parsed_sample"]["to"]:
-        sample.add_to(display_name=address["display_name"],
-                      address=address["address"])
+        sample.add_to(display_name=address["display_name"], address=address["address"])
     for address in forensic_report["parsed_sample"]["reply_to"]:
-        sample.add_reply_to(display_name=address["display_name"],
-                            address=address["address"])
+        sample.add_reply_to(
+            display_name=address["display_name"], address=address["address"]
+        )
     for address in forensic_report["parsed_sample"]["cc"]:
-        sample.add_cc(display_name=address["display_name"],
-                      address=address["address"])
+        sample.add_cc(display_name=address["display_name"], address=address["address"])
     for address in forensic_report["parsed_sample"]["bcc"]:
-        sample.add_bcc(display_name=address["display_name"],
-                       address=address["address"])
+        sample.add_bcc(display_name=address["display_name"], address=address["address"])
     for attachment in forensic_report["parsed_sample"]["attachments"]:
-        sample.add_attachment(filename=attachment["filename"],
-                              content_type=attachment["mail_content_type"],
-                              sha256=attachment["sha256"])
+        sample.add_attachment(
+            filename=attachment["filename"],
+            content_type=attachment["mail_content_type"],
+            sha256=attachment["sha256"],
+        )
     try:
         forensic_doc = _ForensicReportDoc(
             feedback_type=forensic_report["feedback_type"],
@@ -699,12 +736,11 @@ 

Source code for parsedmarc.elastic

             source_country=forensic_report["source"]["country"],
             source_reverse_dns=forensic_report["source"]["reverse_dns"],
             source_base_domain=forensic_report["source"]["base_domain"],
-            authentication_mechanisms=forensic_report[
-                "authentication_mechanisms"],
+            authentication_mechanisms=forensic_report["authentication_mechanisms"],
             auth_failure=forensic_report["auth_failure"],
             dkim_domain=forensic_report["dkim_domain"],
             original_rcpt_to=forensic_report["original_rcpt_to"],
-            sample=sample
+            sample=sample,
         )
 
         index = "dmarc_forensic"
@@ -717,26 +753,32 @@ 

Source code for parsedmarc.elastic

         else:
             index_date = arrival_date.strftime("%Y-%m-%d")
         index = "{0}-{1}".format(index, index_date)
-        index_settings = dict(number_of_shards=number_of_shards,
-                              number_of_replicas=number_of_replicas)
+        index_settings = dict(
+            number_of_shards=number_of_shards, number_of_replicas=number_of_replicas
+        )
         create_indexes([index], index_settings)
         forensic_doc.meta.index = index
         try:
             forensic_doc.save()
         except Exception as e:
-            raise ElasticsearchError(
-                "Elasticsearch error: {0}".format(e.__str__()))
+            raise ElasticsearchError("Elasticsearch error: {0}".format(e.__str__()))
     except KeyError as e:
         raise InvalidForensicReport(
-            "Forensic report missing required field: {0}".format(e.__str__()))
+ "Forensic report missing required field: {0}".format(e.__str__()) + )
-
[docs]def save_smtp_tls_report_to_elasticsearch(report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_smtp_tls_report_to_elasticsearch( + report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ Saves a parsed SMTP TLS report to Elasticsearch @@ -754,10 +796,8 @@

Source code for parsedmarc.elastic

     logger.info("Saving smtp tls report to Elasticsearch")
     org_name = report["organization_name"]
     report_id = report["report_id"]
-    begin_date = human_timestamp_to_datetime(report["begin_date"],
-                                             to_utc=True)
-    end_date = human_timestamp_to_datetime(report["end_date"],
-                                           to_utc=True)
+    begin_date = human_timestamp_to_datetime(report["begin_date"], to_utc=True)
+    end_date = human_timestamp_to_datetime(report["end_date"], to_utc=True)
     begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ")
     end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ")
     if monthly_indexes:
@@ -786,15 +826,19 @@ 

Source code for parsedmarc.elastic

     try:
         existing = search.execute()
     except Exception as error_:
-        raise ElasticsearchError("Elasticsearch's search for existing report \
-            error: {}".format(error_.__str__()))
+        raise ElasticsearchError(
+            "Elasticsearch's search for existing report \
+            error: {}".format(error_.__str__())
+        )
 
     if len(existing) > 0:
-        raise AlreadySaved(f"An SMTP TLS report ID {report_id} from "
-                           f" {org_name} with a date range of "
-                           f"{begin_date_human} UTC to "
-                           f"{end_date_human} UTC already "
-                           "exists in Elasticsearch")
+        raise AlreadySaved(
+            f"An SMTP TLS report ID {report_id} from "
+            f" {org_name} with a date range of "
+            f"{begin_date_human} UTC to "
+            f"{end_date_human} UTC already "
+            "exists in Elasticsearch"
+        )
 
     index = "smtp_tls"
     if index_suffix:
@@ -802,8 +846,9 @@ 

Source code for parsedmarc.elastic

     if index_prefix:
         index = "{0}{1}".format(index_prefix, index)
     index = "{0}-{1}".format(index, index_date)
-    index_settings = dict(number_of_shards=number_of_shards,
-                          number_of_replicas=number_of_replicas)
+    index_settings = dict(
+        number_of_shards=number_of_shards, number_of_replicas=number_of_replicas
+    )
 
     smtp_tls_doc = _SMTPTLSReportDoc(
         org_name=report["organization_name"],
@@ -811,10 +856,10 @@ 

Source code for parsedmarc.elastic

         date_begin=report["begin_date"],
         date_end=report["end_date"],
         contact_info=report["contact_info"],
-        report_id=report["report_id"]
+        report_id=report["report_id"],
     )
 
-    for policy in report['policies']:
+    for policy in report["policies"]:
         policy_strings = None
         mx_host_patterns = None
         if "policy_strings" in policy:
@@ -827,7 +872,7 @@ 

Source code for parsedmarc.elastic

             succesful_session_count=policy["successful_session_count"],
             failed_session_count=policy["failed_session_count"],
             policy_string=policy_strings,
-            mx_host_patterns=mx_host_patterns
+            mx_host_patterns=mx_host_patterns,
         )
         if "failure_details" in policy:
             for failure_detail in policy["failure_details"]:
@@ -840,11 +885,11 @@ 

Source code for parsedmarc.elastic

                 sending_mta_ip = None
 
                 if "receiving_mx_hostname" in failure_detail:
-                    receiving_mx_hostname = failure_detail[
-                        "receiving_mx_hostname"]
+                    receiving_mx_hostname = failure_detail["receiving_mx_hostname"]
                 if "additional_information_uri" in failure_detail:
                     additional_information_uri = failure_detail[
-                        "additional_information_uri"]
+                        "additional_information_uri"
+                    ]
                 if "failure_reason_code" in failure_detail:
                     failure_reason_code = failure_detail["failure_reason_code"]
                 if "ip_address" in failure_detail:
@@ -860,12 +905,11 @@ 

Source code for parsedmarc.elastic

                     ip_address=ip_address,
                     receiving_ip=receiving_ip,
                     receiving_mx_helo=receiving_mx_helo,
-                    failed_session_count=failure_detail[
-                        "failed_session_count"],
+                    failed_session_count=failure_detail["failed_session_count"],
                     sending_mta_ip=sending_mta_ip,
                     receiving_mx_hostname=receiving_mx_hostname,
                     additional_information_uri=additional_information_uri,
-                    failure_reason_code=failure_reason_code
+                    failure_reason_code=failure_reason_code,
                 )
         smtp_tls_doc.policies.append(policy_doc)
 
@@ -875,8 +919,8 @@ 

Source code for parsedmarc.elastic

     try:
         smtp_tls_doc.save()
     except Exception as e:
-        raise ElasticsearchError(
-            "Elasticsearch error: {0}".format(e.__str__()))
+ raise ElasticsearchError("Elasticsearch error: {0}".format(e.__str__()))
+
diff --git a/_modules/parsedmarc/opensearch.html b/_modules/parsedmarc/opensearch.html index 1a98ebec..694d1607 100644 --- a/_modules/parsedmarc/opensearch.html +++ b/_modules/parsedmarc/opensearch.html @@ -1,23 +1,20 @@ + + - + - parsedmarc.opensearch — parsedmarc 8.15.0 documentation - - + parsedmarc.opensearch — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -90,8 +84,21 @@

Source code for parsedmarc.opensearch

 
 from collections import OrderedDict
 
-from opensearchpy import Q, connections, Object, Document, Index, Nested, \
-    InnerDoc, Integer, Text, Boolean, Ip, Date, Search
+from opensearchpy import (
+    Q,
+    connections,
+    Object,
+    Document,
+    Index,
+    Nested,
+    InnerDoc,
+    Integer,
+    Text,
+    Boolean,
+    Ip,
+    Date,
+    Search,
+)
 from opensearchpy.helpers import reindex
 
 from parsedmarc.log import logger
@@ -99,10 +106,13 @@ 

Source code for parsedmarc.opensearch

 from parsedmarc import InvalidForensicReport
 
 
-
[docs]class OpenSearchError(Exception): +
+[docs] +class OpenSearchError(Exception): """Raised when an OpenSearch error occurs"""
+ class _PolicyOverride(InnerDoc): type = Text() comment = Text() @@ -163,24 +173,21 @@

Source code for parsedmarc.opensearch

     spf_results = Nested(_SPFResult)
 
     def add_policy_override(self, type_, comment):
-        self.policy_overrides.append(_PolicyOverride(type=type_,
-                                                     comment=comment))
+        self.policy_overrides.append(_PolicyOverride(type=type_, comment=comment))
 
     def add_dkim_result(self, domain, selector, result):
-        self.dkim_results.append(_DKIMResult(domain=domain,
-                                             selector=selector,
-                                             result=result))
+        self.dkim_results.append(
+            _DKIMResult(domain=domain, selector=selector, result=result)
+        )
 
     def add_spf_result(self, domain, scope, result):
-        self.spf_results.append(_SPFResult(domain=domain,
-                                           scope=scope,
-                                           result=result))
+        self.spf_results.append(_SPFResult(domain=domain, scope=scope, result=result))
 
-    def save(self, ** kwargs):
+    def save(self, **kwargs):
         self.passed_dmarc = False
         self.passed_dmarc = self.spf_aligned or self.dkim_aligned
 
-        return super().save(** kwargs)
+        return super().save(**kwargs)
 
 
 class _EmailAddressDoc(InnerDoc):
@@ -210,24 +217,25 @@ 

Source code for parsedmarc.opensearch

     attachments = Nested(_EmailAttachmentDoc)
 
     def add_to(self, display_name, address):
-        self.to.append(_EmailAddressDoc(display_name=display_name,
-                                        address=address))
+        self.to.append(_EmailAddressDoc(display_name=display_name, address=address))
 
     def add_reply_to(self, display_name, address):
-        self.reply_to.append(_EmailAddressDoc(display_name=display_name,
-                                              address=address))
+        self.reply_to.append(
+            _EmailAddressDoc(display_name=display_name, address=address)
+        )
 
     def add_cc(self, display_name, address):
-        self.cc.append(_EmailAddressDoc(display_name=display_name,
-                                        address=address))
+        self.cc.append(_EmailAddressDoc(display_name=display_name, address=address))
 
     def add_bcc(self, display_name, address):
-        self.bcc.append(_EmailAddressDoc(display_name=display_name,
-                                         address=address))
+        self.bcc.append(_EmailAddressDoc(display_name=display_name, address=address))
 
     def add_attachment(self, filename, content_type, sha256):
-        self.attachments.append(_EmailAttachmentDoc(filename=filename,
-                                content_type=content_type, sha256=sha256))
+        self.attachments.append(
+            _EmailAttachmentDoc(
+                filename=filename, content_type=content_type, sha256=sha256
+            )
+        )
 
 
 class _ForensicReportDoc(Document):
@@ -272,13 +280,17 @@ 

Source code for parsedmarc.opensearch

     failed_session_count = Integer()
     failure_details = Nested(_SMTPTLSFailureDetailsDoc)
 
-    def add_failure_details(self, result_type, ip_address,
-                            receiving_ip,
-                            receiving_mx_helo,
-                            failed_session_count,
-                            receiving_mx_hostname=None,
-                            additional_information_uri=None,
-                            failure_reason_code=None):
+    def add_failure_details(
+        self,
+        result_type,
+        ip_address,
+        receiving_ip,
+        receiving_mx_helo,
+        failed_session_count,
+        receiving_mx_hostname=None,
+        additional_information_uri=None,
+        failure_reason_code=None,
+    ):
         self.failure_details.append(
             result_type=result_type,
             ip_address=ip_address,
@@ -287,12 +299,11 @@ 

Source code for parsedmarc.opensearch

             receiving_ip=receiving_ip,
             failed_session_count=failed_session_count,
             additional_information=additional_information_uri,
-            failure_reason_code=failure_reason_code
+            failure_reason_code=failure_reason_code,
         )
 
 
 class _SMTPTLSFailureReportDoc(Document):
-
     class Index:
         name = "smtp_tls"
 
@@ -304,27 +315,45 @@ 

Source code for parsedmarc.opensearch

     report_id = Text()
     policies = Nested(_SMTPTLSPolicyDoc)
 
-    def add_policy(self, policy_type, policy_domain,
-                   successful_session_count,
-                   failed_session_count,
-                   policy_string=None,
-                   mx_host_patterns=None,
-                   failure_details=None):
-        self.policies.append(policy_type=policy_type,
-                             policy_domain=policy_domain,
-                             successful_session_count=successful_session_count,
-                             failed_session_count=failed_session_count,
-                             policy_string=policy_string,
-                             mx_host_patterns=mx_host_patterns,
-                             failure_details=failure_details)
-
-
-
[docs]class AlreadySaved(ValueError): + def add_policy( + self, + policy_type, + policy_domain, + successful_session_count, + failed_session_count, + policy_string=None, + mx_host_patterns=None, + failure_details=None, + ): + self.policies.append( + policy_type=policy_type, + policy_domain=policy_domain, + successful_session_count=successful_session_count, + failed_session_count=failed_session_count, + policy_string=policy_string, + mx_host_patterns=mx_host_patterns, + failure_details=failure_details, + ) + + +
+[docs] +class AlreadySaved(ValueError): """Raised when a report to be saved matches an existing report"""
-
[docs]def set_hosts(hosts, use_ssl=False, ssl_cert_path=None, - username=None, password=None, apiKey=None, timeout=60.0): + +
+[docs] +def set_hosts( + hosts, + use_ssl=False, + ssl_cert_path=None, + username=None, + password=None, + apiKey=None, + timeout=60.0, +): """ Sets the OpenSearch hosts to use @@ -339,25 +368,25 @@

Source code for parsedmarc.opensearch

     """
     if not isinstance(hosts, list):
         hosts = [hosts]
-    conn_params = {
-        "hosts": hosts,
-        "timeout": timeout
-    }
+    conn_params = {"hosts": hosts, "timeout": timeout}
     if use_ssl:
-        conn_params['use_ssl'] = True
+        conn_params["use_ssl"] = True
         if ssl_cert_path:
-            conn_params['verify_certs'] = True
-            conn_params['ca_certs'] = ssl_cert_path
+            conn_params["verify_certs"] = True
+            conn_params["ca_certs"] = ssl_cert_path
         else:
-            conn_params['verify_certs'] = False
+            conn_params["verify_certs"] = False
     if username:
-        conn_params['http_auth'] = (username+":"+password)
+        conn_params["http_auth"] = username + ":" + password
     if apiKey:
-        conn_params['api_key'] = apiKey
+        conn_params["api_key"] = apiKey
     connections.create_connection(**conn_params)
-
[docs]def create_indexes(names, settings=None): + +
+[docs] +def create_indexes(names, settings=None): """ Create OpenSearch indexes @@ -372,17 +401,18 @@

Source code for parsedmarc.opensearch

             if not index.exists():
                 logger.debug("Creating OpenSearch index: {0}".format(name))
                 if settings is None:
-                    index.settings(number_of_shards=1,
-                                   number_of_replicas=0)
+                    index.settings(number_of_shards=1, number_of_replicas=0)
                 else:
                     index.settings(**settings)
                 index.create()
         except Exception as e:
-            raise OpenSearchError(
-                "OpenSearch error: {0}".format(e.__str__()))
+ raise OpenSearchError("OpenSearch error: {0}".format(e.__str__()))
-
[docs]def migrate_indexes(aggregate_indexes=None, forensic_indexes=None): + +
+[docs] +def migrate_indexes(aggregate_indexes=None, forensic_indexes=None): """ Updates index mappings @@ -411,33 +441,34 @@

Source code for parsedmarc.opensearch

         fo_type = fo_mapping["type"]
         if fo_type == "long":
             new_index_name = "{0}-v{1}".format(aggregate_index_name, version)
-            body = {"properties": {"published_policy.fo": {
-                "type": "text",
-                "fields": {
-                    "keyword": {
-                        "type": "keyword",
-                        "ignore_above": 256
+            body = {
+                "properties": {
+                    "published_policy.fo": {
+                        "type": "text",
+                        "fields": {"keyword": {"type": "keyword", "ignore_above": 256}},
                     }
                 }
             }
-            }
-            }
             Index(new_index_name).create()
             Index(new_index_name).put_mapping(doc_type=doc, body=body)
-            reindex(connections.get_connection(), aggregate_index_name,
-                    new_index_name)
+            reindex(connections.get_connection(), aggregate_index_name, new_index_name)
             Index(aggregate_index_name).delete()
 
     for forensic_index in forensic_indexes:
         pass
-
[docs]def save_aggregate_report_to_opensearch(aggregate_report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_aggregate_report_to_opensearch( + aggregate_report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ Saves a parsed DMARC aggregate report to OpenSearch @@ -458,10 +489,8 @@

Source code for parsedmarc.opensearch

     org_name = metadata["org_name"]
     report_id = metadata["report_id"]
     domain = aggregate_report["policy_published"]["domain"]
-    begin_date = human_timestamp_to_datetime(metadata["begin_date"],
-                                             to_utc=True)
-    end_date = human_timestamp_to_datetime(metadata["end_date"],
-                                           to_utc=True)
+    begin_date = human_timestamp_to_datetime(metadata["begin_date"], to_utc=True)
+    end_date = human_timestamp_to_datetime(metadata["end_date"], to_utc=True)
     begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ")
     end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ")
     if monthly_indexes:
@@ -470,8 +499,7 @@ 

Source code for parsedmarc.opensearch

         index_date = begin_date.strftime("%Y-%m-%d")
     aggregate_report["begin_date"] = begin_date
     aggregate_report["end_date"] = end_date
-    date_range = [aggregate_report["begin_date"],
-                  aggregate_report["end_date"]]
+    date_range = [aggregate_report["begin_date"], aggregate_report["end_date"]]
 
     org_name_query = Q(dict(match_phrase=dict(org_name=org_name)))
     report_id_query = Q(dict(match_phrase=dict(report_id=report_id)))
@@ -493,18 +521,20 @@ 

Source code for parsedmarc.opensearch

     try:
         existing = search.execute()
     except Exception as error_:
-        raise OpenSearchError("OpenSearch's search for existing report \
-            error: {}".format(error_.__str__()))
+        raise OpenSearchError(
+            "OpenSearch's search for existing report \
+            error: {}".format(error_.__str__())
+        )
 
     if len(existing) > 0:
-        raise AlreadySaved("An aggregate report ID {0} from {1} about {2} "
-                           "with a date range of {3} UTC to {4} UTC already "
-                           "exists in "
-                           "OpenSearch".format(report_id,
-                                               org_name,
-                                               domain,
-                                               begin_date_human,
-                                               end_date_human))
+        raise AlreadySaved(
+            "An aggregate report ID {0} from {1} about {2} "
+            "with a date range of {3} UTC to {4} UTC already "
+            "exists in "
+            "OpenSearch".format(
+                report_id, org_name, domain, begin_date_human, end_date_human
+            )
+        )
     published_policy = _PublishedPolicy(
         domain=aggregate_report["policy_published"]["domain"],
         adkim=aggregate_report["policy_published"]["adkim"],
@@ -512,7 +542,7 @@ 

Source code for parsedmarc.opensearch

         p=aggregate_report["policy_published"]["p"],
         sp=aggregate_report["policy_published"]["sp"],
         pct=aggregate_report["policy_published"]["pct"],
-        fo=aggregate_report["policy_published"]["fo"]
+        fo=aggregate_report["policy_published"]["fo"],
     )
 
     for record in aggregate_report["records"]:
@@ -535,28 +565,33 @@ 

Source code for parsedmarc.opensearch

             source_name=record["source"]["name"],
             message_count=record["count"],
             disposition=record["policy_evaluated"]["disposition"],
-            dkim_aligned=record["policy_evaluated"]["dkim"] is not None and
-            record["policy_evaluated"]["dkim"].lower() == "pass",
-            spf_aligned=record["policy_evaluated"]["spf"] is not None and
-            record["policy_evaluated"]["spf"].lower() == "pass",
+            dkim_aligned=record["policy_evaluated"]["dkim"] is not None
+            and record["policy_evaluated"]["dkim"].lower() == "pass",
+            spf_aligned=record["policy_evaluated"]["spf"] is not None
+            and record["policy_evaluated"]["spf"].lower() == "pass",
             header_from=record["identifiers"]["header_from"],
             envelope_from=record["identifiers"]["envelope_from"],
-            envelope_to=record["identifiers"]["envelope_to"]
+            envelope_to=record["identifiers"]["envelope_to"],
         )
 
         for override in record["policy_evaluated"]["policy_override_reasons"]:
-            agg_doc.add_policy_override(type_=override["type"],
-                                        comment=override["comment"])
+            agg_doc.add_policy_override(
+                type_=override["type"], comment=override["comment"]
+            )
 
         for dkim_result in record["auth_results"]["dkim"]:
-            agg_doc.add_dkim_result(domain=dkim_result["domain"],
-                                    selector=dkim_result["selector"],
-                                    result=dkim_result["result"])
+            agg_doc.add_dkim_result(
+                domain=dkim_result["domain"],
+                selector=dkim_result["selector"],
+                result=dkim_result["result"],
+            )
 
         for spf_result in record["auth_results"]["spf"]:
-            agg_doc.add_spf_result(domain=spf_result["domain"],
-                                   scope=spf_result["scope"],
-                                   result=spf_result["result"])
+            agg_doc.add_spf_result(
+                domain=spf_result["domain"],
+                scope=spf_result["scope"],
+                result=spf_result["result"],
+            )
 
         index = "dmarc_aggregate"
         if index_suffix:
@@ -564,41 +599,46 @@ 

Source code for parsedmarc.opensearch

         if index_prefix:
             index = "{0}{1}".format(index_prefix, index)
         index = "{0}-{1}".format(index, index_date)
-        index_settings = dict(number_of_shards=number_of_shards,
-                              number_of_replicas=number_of_replicas)
+        index_settings = dict(
+            number_of_shards=number_of_shards, number_of_replicas=number_of_replicas
+        )
         create_indexes([index], index_settings)
         agg_doc.meta.index = index
 
         try:
             agg_doc.save()
         except Exception as e:
-            raise OpenSearchError(
-                "OpenSearch error: {0}".format(e.__str__()))
+ raise OpenSearchError("OpenSearch error: {0}".format(e.__str__()))
-
[docs]def save_forensic_report_to_opensearch(forensic_report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): + +
+[docs] +def save_forensic_report_to_opensearch( + forensic_report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ - Saves a parsed DMARC forensic report to OpenSearch - - Args: - forensic_report (OrderedDict): A parsed forensic report - index_suffix (str): The suffix of the name of the index to save to - index_prefix (str): The prefix of the name of the index to save to - monthly_indexes (bool): Use monthly indexes instead of daily - indexes - number_of_shards (int): The number of shards to use in the index - number_of_replicas (int): The number of replicas to use in the - index - - Raises: - AlreadySaved + Saves a parsed DMARC forensic report to OpenSearch + + Args: + forensic_report (OrderedDict): A parsed forensic report + index_suffix (str): The suffix of the name of the index to save to + index_prefix (str): The prefix of the name of the index to save to + monthly_indexes (bool): Use monthly indexes instead of daily + indexes + number_of_shards (int): The number of shards to use in the index + number_of_replicas (int): The number of replicas to use in the + index + + Raises: + AlreadySaved - """ + """ logger.info("Saving forensic report to OpenSearch") forensic_report = forensic_report.copy() sample_date = None @@ -643,12 +683,12 @@

Source code for parsedmarc.opensearch

     existing = search.execute()
 
     if len(existing) > 0:
-        raise AlreadySaved("A forensic sample to {0} from {1} "
-                           "with a subject of {2} and arrival date of {3} "
-                           "already exists in "
-                           "OpenSearch".format(
-                               to_, from_, subject, arrival_date_human
-                               ))
+        raise AlreadySaved(
+            "A forensic sample to {0} from {1} "
+            "with a subject of {2} and arrival date of {3} "
+            "already exists in "
+            "OpenSearch".format(to_, from_, subject, arrival_date_human)
+        )
 
     parsed_sample = forensic_report["parsed_sample"]
     sample = _ForensicSampleDoc(
@@ -658,25 +698,25 @@ 

Source code for parsedmarc.opensearch

         date=sample_date,
         subject=forensic_report["parsed_sample"]["subject"],
         filename_safe_subject=parsed_sample["filename_safe_subject"],
-        body=forensic_report["parsed_sample"]["body"]
+        body=forensic_report["parsed_sample"]["body"],
     )
 
     for address in forensic_report["parsed_sample"]["to"]:
-        sample.add_to(display_name=address["display_name"],
-                      address=address["address"])
+        sample.add_to(display_name=address["display_name"], address=address["address"])
     for address in forensic_report["parsed_sample"]["reply_to"]:
-        sample.add_reply_to(display_name=address["display_name"],
-                            address=address["address"])
+        sample.add_reply_to(
+            display_name=address["display_name"], address=address["address"]
+        )
     for address in forensic_report["parsed_sample"]["cc"]:
-        sample.add_cc(display_name=address["display_name"],
-                      address=address["address"])
+        sample.add_cc(display_name=address["display_name"], address=address["address"])
     for address in forensic_report["parsed_sample"]["bcc"]:
-        sample.add_bcc(display_name=address["display_name"],
-                       address=address["address"])
+        sample.add_bcc(display_name=address["display_name"], address=address["address"])
     for attachment in forensic_report["parsed_sample"]["attachments"]:
-        sample.add_attachment(filename=attachment["filename"],
-                              content_type=attachment["mail_content_type"],
-                              sha256=attachment["sha256"])
+        sample.add_attachment(
+            filename=attachment["filename"],
+            content_type=attachment["mail_content_type"],
+            sha256=attachment["sha256"],
+        )
     try:
         forensic_doc = _ForensicReportDoc(
             feedback_type=forensic_report["feedback_type"],
@@ -692,12 +732,11 @@ 

Source code for parsedmarc.opensearch

             source_country=forensic_report["source"]["country"],
             source_reverse_dns=forensic_report["source"]["reverse_dns"],
             source_base_domain=forensic_report["source"]["base_domain"],
-            authentication_mechanisms=forensic_report[
-                "authentication_mechanisms"],
+            authentication_mechanisms=forensic_report["authentication_mechanisms"],
             auth_failure=forensic_report["auth_failure"],
             dkim_domain=forensic_report["dkim_domain"],
             original_rcpt_to=forensic_report["original_rcpt_to"],
-            sample=sample
+            sample=sample,
         )
 
         index = "dmarc_forensic"
@@ -710,26 +749,32 @@ 

Source code for parsedmarc.opensearch

         else:
             index_date = arrival_date.strftime("%Y-%m-%d")
         index = "{0}-{1}".format(index, index_date)
-        index_settings = dict(number_of_shards=number_of_shards,
-                              number_of_replicas=number_of_replicas)
+        index_settings = dict(
+            number_of_shards=number_of_shards, number_of_replicas=number_of_replicas
+        )
         create_indexes([index], index_settings)
         forensic_doc.meta.index = index
         try:
             forensic_doc.save()
         except Exception as e:
-            raise OpenSearchError(
-                "OpenSearch error: {0}".format(e.__str__()))
+            raise OpenSearchError("OpenSearch error: {0}".format(e.__str__()))
     except KeyError as e:
         raise InvalidForensicReport(
-            "Forensic report missing required field: {0}".format(e.__str__()))
+ "Forensic report missing required field: {0}".format(e.__str__()) + )
+ -
[docs]def save_smtp_tls_report_to_opensearch(report, - index_suffix=None, - index_prefix=None, - monthly_indexes=False, - number_of_shards=1, - number_of_replicas=0): +
+[docs] +def save_smtp_tls_report_to_opensearch( + report, + index_suffix=None, + index_prefix=None, + monthly_indexes=False, + number_of_shards=1, + number_of_replicas=0, +): """ Saves a parsed SMTP TLS report to OpenSearch @@ -747,10 +792,8 @@

Source code for parsedmarc.opensearch

     logger.info("Saving aggregate report to OpenSearch")
     org_name = report["org_name"]
     report_id = report["report_id"]
-    begin_date = human_timestamp_to_datetime(report["begin_date"],
-                                             to_utc=True)
-    end_date = human_timestamp_to_datetime(report["end_date"],
-                                           to_utc=True)
+    begin_date = human_timestamp_to_datetime(report["begin_date"], to_utc=True)
+    end_date = human_timestamp_to_datetime(report["end_date"], to_utc=True)
     begin_date_human = begin_date.strftime("%Y-%m-%d %H:%M:%SZ")
     end_date_human = end_date.strftime("%Y-%m-%d %H:%M:%SZ")
     if monthly_indexes:
@@ -779,15 +822,19 @@ 

Source code for parsedmarc.opensearch

     try:
         existing = search.execute()
     except Exception as error_:
-        raise OpenSearchError("OpenSearch's search for existing report \
-            error: {}".format(error_.__str__()))
+        raise OpenSearchError(
+            "OpenSearch's search for existing report \
+            error: {}".format(error_.__str__())
+        )
 
     if len(existing) > 0:
-        raise AlreadySaved(f"An SMTP TLS report ID {report_id} from "
-                           f" {org_name} with a date range of "
-                           f"{begin_date_human} UTC to "
-                           f"{end_date_human} UTC already "
-                           "exists in OpenSearch")
+        raise AlreadySaved(
+            f"An SMTP TLS report ID {report_id} from "
+            f" {org_name} with a date range of "
+            f"{begin_date_human} UTC to "
+            f"{end_date_human} UTC already "
+            "exists in OpenSearch"
+        )
 
     index = "smtp_tls"
     if index_suffix:
@@ -795,8 +842,9 @@ 

Source code for parsedmarc.opensearch

     if index_prefix:
         index = "{0}{1}".format(index_prefix, index)
     index = "{0}-{1}".format(index, index_date)
-    index_settings = dict(number_of_shards=number_of_shards,
-                          number_of_replicas=number_of_replicas)
+    index_settings = dict(
+        number_of_shards=number_of_shards, number_of_replicas=number_of_replicas
+    )
 
     smtp_tls_doc = _SMTPTLSFailureReportDoc(
         organization_name=report["organization_name"],
@@ -804,10 +852,10 @@ 

Source code for parsedmarc.opensearch

         date_begin=report["date_begin"],
         date_end=report["date_end"],
         contact_info=report["contact_info"],
-        report_id=report["report_id"]
+        report_id=report["report_id"],
     )
 
-    for policy in report['policies']:
+    for policy in report["policies"]:
         policy_strings = None
         mx_host_patterns = None
         if "policy_strings" in policy:
@@ -818,7 +866,7 @@ 

Source code for parsedmarc.opensearch

             policy_domain=policy["policy_domain"],
             policy_type=policy["policy_type"],
             policy_string=policy_strings,
-            mx_host_patterns=mx_host_patterns
+            mx_host_patterns=mx_host_patterns,
         )
         if "failure_details" in policy:
             failure_details = policy["failure_details"]
@@ -826,11 +874,11 @@ 

Source code for parsedmarc.opensearch

             additional_information_uri = None
             failure_reason_code = None
             if "receiving_mx_hostname" in failure_details:
-                receiving_mx_hostname = failure_details[
-                    "receiving_mx_hostname"]
+                receiving_mx_hostname = failure_details["receiving_mx_hostname"]
             if "additional_information_uri" in failure_details:
                 additional_information_uri = failure_details[
-                    "additional_information_uri"]
+                    "additional_information_uri"
+                ]
             if "failure_reason_code" in failure_details:
                 failure_reason_code = failure_details["failure_reason_code"]
             policy_doc.add_failure_details(
@@ -841,7 +889,7 @@ 

Source code for parsedmarc.opensearch

                 failed_session_count=failure_details["failed_session_count"],
                 receiving_mx_hostname=receiving_mx_hostname,
                 additional_information_uri=additional_information_uri,
-                failure_reason_code=failure_reason_code
+                failure_reason_code=failure_reason_code,
             )
         smtp_tls_doc.policies.append(policy_doc)
 
@@ -851,8 +899,8 @@ 

Source code for parsedmarc.opensearch

     try:
         smtp_tls_doc.save()
     except Exception as e:
-        raise OpenSearchError(
-            "OpenSearch error: {0}".format(e.__str__()))
+ raise OpenSearchError("OpenSearch error: {0}".format(e.__str__()))
+
diff --git a/_modules/parsedmarc/splunk.html b/_modules/parsedmarc/splunk.html index 0cb91820..999aa3c5 100644 --- a/_modules/parsedmarc/splunk.html +++ b/_modules/parsedmarc/splunk.html @@ -1,23 +1,20 @@ + + - + - parsedmarc.splunk — parsedmarc 8.15.0 documentation - - + parsedmarc.splunk — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -100,18 +94,24 @@

Source code for parsedmarc.splunk

 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
 
-
[docs]class SplunkError(RuntimeError): +
+[docs] +class SplunkError(RuntimeError): """Raised when a Splunk API error occurs"""
-
[docs]class HECClient(object): + +
+[docs] +class HECClient(object): """A client for a Splunk HTTP Events Collector (HEC)""" # http://docs.splunk.com/Documentation/Splunk/latest/Data/AboutHEC # http://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTinput#services.2Fcollector - def __init__(self, url, access_token, index, - source="parsedmarc", verify=True, timeout=60): + def __init__( + self, url, access_token, index, source="parsedmarc", verify=True, timeout=60 + ): """ Initializes the HECClient @@ -125,8 +125,9 @@

Source code for parsedmarc.splunk

                 data before giving up
         """
         url = urlparse(url)
-        self.url = "{0}://{1}/services/collector/event/1.0".format(url.scheme,
-                                                                   url.netloc)
+        self.url = "{0}://{1}/services/collector/event/1.0".format(
+            url.scheme, url.netloc
+        )
         self.access_token = access_token.lstrip("Splunk ")
         self.index = index
         self.host = socket.getfqdn()
@@ -134,15 +135,16 @@ 

Source code for parsedmarc.splunk

         self.session = requests.Session()
         self.timeout = timeout
         self.session.verify = verify
-        self._common_data = dict(host=self.host, source=self.source,
-                                 index=self.index)
+        self._common_data = dict(host=self.host, source=self.source, index=self.index)
 
         self.session.headers = {
             "User-Agent": "parsedmarc/{0}".format(__version__),
-            "Authorization": "Splunk {0}".format(self.access_token)
+            "Authorization": "Splunk {0}".format(self.access_token),
         }
 
-
[docs] def save_aggregate_reports_to_splunk(self, aggregate_reports): +
+[docs] + def save_aggregate_reports_to_splunk(self, aggregate_reports): """ Saves aggregate DMARC reports to Splunk @@ -166,36 +168,26 @@

Source code for parsedmarc.splunk

                 for metadata in report["report_metadata"]:
                     new_report[metadata] = report["report_metadata"][metadata]
                 new_report["published_policy"] = report["policy_published"]
-                new_report["source_ip_address"] = record["source"][
-                    "ip_address"]
+                new_report["source_ip_address"] = record["source"]["ip_address"]
                 new_report["source_country"] = record["source"]["country"]
-                new_report["source_reverse_dns"] = record["source"][
-                    "reverse_dns"]
-                new_report["source_base_domain"] = record["source"][
-                    "base_domain"]
+                new_report["source_reverse_dns"] = record["source"]["reverse_dns"]
+                new_report["source_base_domain"] = record["source"]["base_domain"]
                 new_report["source_type"] = record["source"]["type"]
                 new_report["source_name"] = record["source"]["name"]
                 new_report["message_count"] = record["count"]
-                new_report["disposition"] = record["policy_evaluated"][
-                    "disposition"
-                ]
+                new_report["disposition"] = record["policy_evaluated"]["disposition"]
                 new_report["spf_aligned"] = record["alignment"]["spf"]
                 new_report["dkim_aligned"] = record["alignment"]["dkim"]
                 new_report["passed_dmarc"] = record["alignment"]["dmarc"]
-                new_report["header_from"] = record["identifiers"][
-                    "header_from"]
-                new_report["envelope_from"] = record["identifiers"][
-                    "envelope_from"]
+                new_report["header_from"] = record["identifiers"]["header_from"]
+                new_report["envelope_from"] = record["identifiers"]["envelope_from"]
                 if "dkim" in record["auth_results"]:
-                    new_report["dkim_results"] = record["auth_results"][
-                        "dkim"]
+                    new_report["dkim_results"] = record["auth_results"]["dkim"]
                 if "spf" in record["auth_results"]:
-                    new_report["spf_results"] = record["auth_results"][
-                        "spf"]
+                    new_report["spf_results"] = record["auth_results"]["spf"]
 
                 data["sourcetype"] = "dmarc:aggregate"
-                timestamp = human_timestamp_to_unix_timestamp(
-                    new_report["begin_date"])
+                timestamp = human_timestamp_to_unix_timestamp(new_report["begin_date"])
                 data["time"] = timestamp
                 data["event"] = new_report.copy()
                 json_str += "{0}\n".format(json.dumps(data))
@@ -203,15 +195,17 @@ 

Source code for parsedmarc.splunk

         if not self.session.verify:
             logger.debug("Skipping certificate verification for Splunk HEC")
         try:
-            response = self.session.post(self.url, data=json_str,
-                                         timeout=self.timeout)
+            response = self.session.post(self.url, data=json_str, timeout=self.timeout)
             response = response.json()
         except Exception as e:
             raise SplunkError(e.__str__())
         if response["code"] != 0:
             raise SplunkError(response["text"])
-
[docs] def save_forensic_reports_to_splunk(self, forensic_reports): + +
+[docs] + def save_forensic_reports_to_splunk(self, forensic_reports): """ Saves forensic DMARC reports to Splunk @@ -230,8 +224,7 @@

Source code for parsedmarc.splunk

         for report in forensic_reports:
             data = self._common_data.copy()
             data["sourcetype"] = "dmarc:forensic"
-            timestamp = human_timestamp_to_unix_timestamp(
-                report["arrival_date_utc"])
+            timestamp = human_timestamp_to_unix_timestamp(report["arrival_date_utc"])
             data["time"] = timestamp
             data["event"] = report.copy()
             json_str += "{0}\n".format(json.dumps(data))
@@ -239,15 +232,17 @@ 

Source code for parsedmarc.splunk

         if not self.session.verify:
             logger.debug("Skipping certificate verification for Splunk HEC")
         try:
-            response = self.session.post(self.url, data=json_str,
-                                         timeout=self.timeout)
+            response = self.session.post(self.url, data=json_str, timeout=self.timeout)
             response = response.json()
         except Exception as e:
             raise SplunkError(e.__str__())
         if response["code"] != 0:
             raise SplunkError(response["text"])
-
[docs] def save_smtp_tls_reports_to_splunk(self, reports): + +
+[docs] + def save_smtp_tls_reports_to_splunk(self, reports): """ Saves aggregate DMARC reports to Splunk @@ -267,8 +262,7 @@

Source code for parsedmarc.splunk

         json_str = ""
         for report in reports:
             data["sourcetype"] = "smtp:tls"
-            timestamp = human_timestamp_to_unix_timestamp(
-                report["begin_date"])
+            timestamp = human_timestamp_to_unix_timestamp(report["begin_date"])
             data["time"] = timestamp
             data["event"] = report.copy()
             json_str += "{0}\n".format(json.dumps(data))
@@ -276,13 +270,14 @@ 

Source code for parsedmarc.splunk

         if not self.session.verify:
             logger.debug("Skipping certificate verification for Splunk HEC")
         try:
-            response = self.session.post(self.url, data=json_str,
-                                         timeout=self.timeout)
+            response = self.session.post(self.url, data=json_str, timeout=self.timeout)
             response = response.json()
         except Exception as e:
             raise SplunkError(e.__str__())
         if response["code"] != 0:
-            raise SplunkError(response["text"])
+ raise SplunkError(response["text"])
+
+
diff --git a/_modules/parsedmarc/utils.html b/_modules/parsedmarc/utils.html index 91259fdf..f8504ac2 100644 --- a/_modules/parsedmarc/utils.html +++ b/_modules/parsedmarc/utils.html @@ -1,23 +1,20 @@ + + - + - parsedmarc.utils — parsedmarc 8.15.0 documentation - - + parsedmarc.utils — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -
- 8.15.0 -
@@ -126,22 +120,30 @@

Source code for parsedmarc.utils

 import parsedmarc.resources.maps
 
 
-parenthesis_regex = re.compile(r'\s*\(.*\)\s*')
+parenthesis_regex = re.compile(r"\s*\(.*\)\s*")
 
 null_file = open(os.devnull, "w")
 mailparser_logger = logging.getLogger("mailparser")
 mailparser_logger.setLevel(logging.CRITICAL)
 
 
-
[docs]class EmailParserError(RuntimeError): +
+[docs] +class EmailParserError(RuntimeError): """Raised when an error parsing the email occurs"""
-
[docs]class DownloadError(RuntimeError): + +
+[docs] +class DownloadError(RuntimeError): """Raised when an error occurs when downloading a file"""
-
[docs]def decode_base64(data): + +
+[docs] +def decode_base64(data): """ Decodes a base64 string, with padding being optional @@ -155,11 +157,14 @@

Source code for parsedmarc.utils

     data = bytes(data, encoding="ascii")
     missing_padding = len(data) % 4
     if missing_padding != 0:
-        data += b'=' * (4 - missing_padding)
+        data += b"=" * (4 - missing_padding)
     return base64.b64decode(data)
-
[docs]def get_base_domain(domain): + +
+[docs] +def get_base_domain(domain): """ Gets the base domain name for the given domain @@ -178,7 +183,10 @@

Source code for parsedmarc.utils

     return psl.privatesuffix(domain)
-
[docs]def query_dns(domain, record_type, cache=None, nameservers=None, timeout=2.0): + +
+[docs] +def query_dns(domain, record_type, cache=None, nameservers=None, timeout=2.0): """ Queries DNS @@ -204,31 +212,45 @@

Source code for parsedmarc.utils

     resolver = dns.resolver.Resolver()
     timeout = float(timeout)
     if nameservers is None:
-        nameservers = ["1.1.1.1", "1.0.0.1",
-                       "2606:4700:4700::1111", "2606:4700:4700::1001",
-                       ]
+        nameservers = [
+            "1.1.1.1",
+            "1.0.0.1",
+            "2606:4700:4700::1111",
+            "2606:4700:4700::1001",
+        ]
     resolver.nameservers = nameservers
     resolver.timeout = timeout
     resolver.lifetime = timeout
     if record_type == "TXT":
-        resource_records = list(map(
-            lambda r: r.strings,
-            resolver.resolve(domain, record_type, lifetime=timeout)))
+        resource_records = list(
+            map(
+                lambda r: r.strings,
+                resolver.resolve(domain, record_type, lifetime=timeout),
+            )
+        )
         _resource_record = [
             resource_record[0][:0].join(resource_record)
-            for resource_record in resource_records if resource_record]
+            for resource_record in resource_records
+            if resource_record
+        ]
         records = [r.decode() for r in _resource_record]
     else:
-        records = list(map(
-            lambda r: r.to_text().replace('"', '').rstrip("."),
-            resolver.resolve(domain, record_type, lifetime=timeout)))
+        records = list(
+            map(
+                lambda r: r.to_text().replace('"', "").rstrip("."),
+                resolver.resolve(domain, record_type, lifetime=timeout),
+            )
+        )
     if cache:
         cache[cache_key] = records
 
     return records
-
[docs]def get_reverse_dns(ip_address, cache=None, nameservers=None, timeout=2.0): + +
+[docs] +def get_reverse_dns(ip_address, cache=None, nameservers=None, timeout=2.0): """ Resolves an IP address to a hostname using a reverse DNS query @@ -245,9 +267,9 @@

Source code for parsedmarc.utils

     hostname = None
     try:
         address = dns.reversename.from_address(ip_address)
-        hostname = query_dns(address, "PTR", cache=cache,
-                             nameservers=nameservers,
-                             timeout=timeout)[0]
+        hostname = query_dns(
+            address, "PTR", cache=cache, nameservers=nameservers, timeout=timeout
+        )[0]
 
     except dns.exception.DNSException as e:
         logger.warning(f"get_reverse_dns({ip_address}) exception: {e}")
@@ -256,7 +278,10 @@ 

Source code for parsedmarc.utils

     return hostname
-
[docs]def timestamp_to_datetime(timestamp): + +
+[docs] +def timestamp_to_datetime(timestamp): """ Converts a UNIX/DMARC timestamp to a Python ``datetime`` object @@ -269,7 +294,10 @@

Source code for parsedmarc.utils

     return datetime.fromtimestamp(int(timestamp))
-
[docs]def timestamp_to_human(timestamp): + +
+[docs] +def timestamp_to_human(timestamp): """ Converts a UNIX/DMARC timestamp to a human-readable string @@ -282,7 +310,10 @@

Source code for parsedmarc.utils

     return timestamp_to_datetime(timestamp).strftime("%Y-%m-%d %H:%M:%S")
-
[docs]def human_timestamp_to_datetime(human_timestamp, to_utc=False): + +
+[docs] +def human_timestamp_to_datetime(human_timestamp, to_utc=False): """ Converts a human-readable timestamp into a Python ``datetime`` object @@ -301,7 +332,10 @@

Source code for parsedmarc.utils

     return dt.astimezone(timezone.utc) if to_utc else dt
-
[docs]def human_timestamp_to_unix_timestamp(human_timestamp): + +
+[docs] +def human_timestamp_to_unix_timestamp(human_timestamp): """ Converts a human-readable timestamp into a UNIX timestamp @@ -315,7 +349,10 @@

Source code for parsedmarc.utils

     return human_timestamp_to_datetime(human_timestamp).timestamp()
-
[docs]def get_ip_address_country(ip_address, db_path=None): + +
+[docs] +def get_ip_address_country(ip_address, db_path=None): """ Returns the ISO code for the country associated with the given IPv4 or IPv6 address @@ -344,9 +381,11 @@

Source code for parsedmarc.utils

     if db_path is not None:
         if os.path.isfile(db_path) is False:
             db_path = None
-            logger.warning(f"No file exists at {db_path}. Falling back to an "
-                           "included copy of the IPDB IP to Country "
-                           "Lite database.")
+            logger.warning(
+                f"No file exists at {db_path}. Falling back to an "
+                "included copy of the IPDB IP to Country "
+                "Lite database."
+            )
 
     if db_path is None:
         for system_path in db_paths:
@@ -355,12 +394,12 @@ 

Source code for parsedmarc.utils

                 break
 
     if db_path is None:
-        with pkg_resources.path(parsedmarc.resources.dbip,
-                                "dbip-country-lite.mmdb") as path:
+        with pkg_resources.path(
+            parsedmarc.resources.dbip, "dbip-country-lite.mmdb"
+        ) as path:
             db_path = path
 
-        db_age = datetime.now() - datetime.fromtimestamp(
-            os.stat(db_path).st_mtime)
+        db_age = datetime.now() - datetime.fromtimestamp(os.stat(db_path).st_mtime)
         if db_age > timedelta(days=30):
             logger.warning("IP database is more than a month old")
 
@@ -376,12 +415,17 @@ 

Source code for parsedmarc.utils

     return country
-
[docs]def get_service_from_reverse_dns_base_domain(base_domain, - always_use_local_file=False, - local_file_path=None, - url=None, - offline=False, - reverse_dns_map=None): + +
+[docs] +def get_service_from_reverse_dns_base_domain( + base_domain, + always_use_local_file=False, + local_file_path=None, + url=None, + offline=False, + reverse_dns_map=None, +): """ Returns the service name of a given base domain name from reverse DNS. @@ -397,28 +441,27 @@

Source code for parsedmarc.utils

         If the service is unknown, the name will be
         the supplied reverse_dns_base_domain and the type will be None
     """
+
     def load_csv(_csv_file):
         reader = csv.DictReader(_csv_file)
         for row in reader:
             key = row["base_reverse_dns"].lower().strip()
-            reverse_dns_map[key] = dict(
-                name=row["name"],
-                type=row["type"])
+            reverse_dns_map[key] = dict(name=row["name"], type=row["type"])
 
     base_domain = base_domain.lower().strip()
     if url is None:
-        url = ("https://raw.githubusercontent.com/domainaware"
-               "/parsedmarc/master/parsedmarc/"
-               "resources/maps/base_reverse_dns_map.csv")
+        url = (
+            "https://raw.githubusercontent.com/domainaware"
+            "/parsedmarc/master/parsedmarc/"
+            "resources/maps/base_reverse_dns_map.csv"
+        )
     if reverse_dns_map is None:
         reverse_dns_map = dict()
     csv_file = io.StringIO()
 
-    if (not (offline or always_use_local_file)
-            and len(reverse_dns_map) == 0):
+    if not (offline or always_use_local_file) and len(reverse_dns_map) == 0:
         try:
-            logger.debug(f"Trying to fetch "
-                         f"reverse DNS map from {url}...")
+            logger.debug(f"Trying to fetch " f"reverse DNS map from {url}...")
             csv_file.write(requests.get(url).text)
             csv_file.seek(0)
             load_csv(csv_file)
@@ -426,8 +469,9 @@ 

Source code for parsedmarc.utils

             logger.warning(f"Failed to fetch reverse DNS map: {e}")
     if len(reverse_dns_map) == 0:
         logger.info("Loading included reverse DNS map...")
-        with pkg_resources.path(parsedmarc.resources.maps,
-                                "base_reverse_dns_map.csv") as path:
+        with pkg_resources.path(
+            parsedmarc.resources.maps, "base_reverse_dns_map.csv"
+        ) as path:
             if local_file_path is not None:
                 path = local_file_path
             with open(path) as csv_file:
@@ -440,15 +484,21 @@ 

Source code for parsedmarc.utils

     return service
-
[docs]def get_ip_address_info(ip_address, - ip_db_path=None, - reverse_dns_map_path=None, - always_use_local_files=False, - reverse_dns_map_url=None, - cache=None, - reverse_dns_map=None, - offline=False, - nameservers=None, timeout=2.0): + +
+[docs] +def get_ip_address_info( + ip_address, + ip_db_path=None, + reverse_dns_map_path=None, + always_use_local_files=False, + reverse_dns_map_url=None, + cache=None, + reverse_dns_map=None, + offline=False, + nameservers=None, + timeout=2.0, +): """ Returns reverse DNS and country information for the given IP address @@ -480,9 +530,9 @@

Source code for parsedmarc.utils

     if offline:
         reverse_dns = None
     else:
-        reverse_dns = get_reverse_dns(ip_address,
-                                      nameservers=nameservers,
-                                      timeout=timeout)
+        reverse_dns = get_reverse_dns(
+            ip_address, nameservers=nameservers, timeout=timeout
+        )
     country = get_ip_address_country(ip_address, db_path=ip_db_path)
     info["country"] = country
     info["reverse_dns"] = reverse_dns
@@ -498,7 +548,8 @@ 

Source code for parsedmarc.utils

                 local_file_path=reverse_dns_map_path,
                 url=reverse_dns_map_url,
                 always_use_local_file=always_use_local_files,
-                reverse_dns_map=reverse_dns_map)
+                reverse_dns_map=reverse_dns_map,
+            )
             info["base_domain"] = base_domain
             info["type"] = service["type"]
             info["name"] = service["name"]
@@ -512,6 +563,7 @@ 

Source code for parsedmarc.utils

     return info
+ def parse_email_address(original_address): if original_address[0] == "": display_name = None @@ -525,13 +577,19 @@

Source code for parsedmarc.utils

         local = address_parts[0].lower()
         domain = address_parts[-1].lower()
 
-    return OrderedDict([("display_name", display_name),
-                        ("address", address),
-                        ("local", local),
-                        ("domain", domain)])
+    return OrderedDict(
+        [
+            ("display_name", display_name),
+            ("address", address),
+            ("local", local),
+            ("domain", domain),
+        ]
+    )
 
 
-
[docs]def get_filename_safe_string(string): +
+[docs] +def get_filename_safe_string(string): """ Converts a string to a string that is safe for a filename @@ -541,8 +599,7 @@

Source code for parsedmarc.utils

     Returns:
         str: A string safe for a filename
     """
-    invalid_filename_chars = ['\\', '/', ':', '"', '*', '?', '|', '\n',
-                              '\r']
+    invalid_filename_chars = ["\\", "/", ":", '"', "*", "?", "|", "\n", "\r"]
     if string is None:
         string = "None"
     for char in invalid_filename_chars:
@@ -554,7 +611,10 @@ 

Source code for parsedmarc.utils

     return string
-
[docs]def is_mbox(path): + +
+[docs] +def is_mbox(path): """ Checks if the given content is an MBOX mailbox file @@ -575,7 +635,10 @@

Source code for parsedmarc.utils

     return _is_mbox
-
[docs]def is_outlook_msg(content): + +
+[docs] +def is_outlook_msg(content): """ Checks if the given content is an Outlook msg OLE/MSG file @@ -586,10 +649,14 @@

Source code for parsedmarc.utils

         bool: A flag that indicates if the file is an Outlook MSG file
     """
     return isinstance(content, bytes) and content.startswith(
-        b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1")
+ b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" + )
+ -
[docs]def convert_outlook_msg(msg_bytes): +
+[docs] +def convert_outlook_msg(msg_bytes): """ Uses the ``msgconvert`` Perl utility to convert an Outlook MS file to standard RFC 822 format @@ -608,14 +675,16 @@

Source code for parsedmarc.utils

     with open("sample.msg", "wb") as msg_file:
         msg_file.write(msg_bytes)
     try:
-        subprocess.check_call(["msgconvert", "sample.msg"],
-                              stdout=null_file, stderr=null_file)
+        subprocess.check_call(
+            ["msgconvert", "sample.msg"], stdout=null_file, stderr=null_file
+        )
         eml_path = "sample.eml"
         with open(eml_path, "rb") as eml_file:
             rfc822 = eml_file.read()
     except FileNotFoundError:
         raise EmailParserError(
-            "Failed to convert Outlook MSG: msgconvert utility not found")
+            "Failed to convert Outlook MSG: msgconvert utility not found"
+        )
     finally:
         os.chdir(orig_dir)
         shutil.rmtree(tmp_dir)
@@ -623,7 +692,10 @@ 

Source code for parsedmarc.utils

     return rfc822
-
[docs]def parse_email(data, strip_attachment_payloads=False): + +
+[docs] +def parse_email(data, strip_attachment_payloads=False): """ A simplified email parser @@ -650,8 +722,7 @@

Source code for parsedmarc.utils

                 if received["date_utc"] is None:
                     del received["date_utc"]
                 else:
-                    received["date_utc"] = received["date_utc"].replace("T",
-                                                                        " ")
+                    received["date_utc"] = received["date_utc"].replace("T", " ")
 
     if "from" not in parsed_email:
         if "From" in parsed_email["headers"]:
@@ -667,33 +738,36 @@ 

Source code for parsedmarc.utils

     else:
         parsed_email["date"] = None
     if "reply_to" in parsed_email:
-        parsed_email["reply_to"] = list(map(lambda x: parse_email_address(x),
-                                            parsed_email["reply_to"]))
+        parsed_email["reply_to"] = list(
+            map(lambda x: parse_email_address(x), parsed_email["reply_to"])
+        )
     else:
         parsed_email["reply_to"] = []
 
     if "to" in parsed_email:
-        parsed_email["to"] = list(map(lambda x: parse_email_address(x),
-                                      parsed_email["to"]))
+        parsed_email["to"] = list(
+            map(lambda x: parse_email_address(x), parsed_email["to"])
+        )
     else:
         parsed_email["to"] = []
 
     if "cc" in parsed_email:
-        parsed_email["cc"] = list(map(lambda x: parse_email_address(x),
-                                      parsed_email["cc"]))
+        parsed_email["cc"] = list(
+            map(lambda x: parse_email_address(x), parsed_email["cc"])
+        )
     else:
         parsed_email["cc"] = []
 
     if "bcc" in parsed_email:
-        parsed_email["bcc"] = list(map(lambda x: parse_email_address(x),
-                                       parsed_email["bcc"]))
+        parsed_email["bcc"] = list(
+            map(lambda x: parse_email_address(x), parsed_email["bcc"])
+        )
     else:
         parsed_email["bcc"] = []
 
     if "delivered_to" in parsed_email:
         parsed_email["delivered_to"] = list(
-            map(lambda x: parse_email_address(x),
-                parsed_email["delivered_to"])
+            map(lambda x: parse_email_address(x), parsed_email["delivered_to"])
         )
 
     if "attachments" not in parsed_email:
@@ -710,9 +784,7 @@ 

Source code for parsedmarc.utils

                             payload = str.encode(payload)
                     attachment["sha256"] = hashlib.sha256(payload).hexdigest()
                 except Exception as e:
-                    logger.debug("Unable to decode attachment: {0}".format(
-                        e.__str__()
-                    ))
+                    logger.debug("Unable to decode attachment: {0}".format(e.__str__()))
         if strip_attachment_payloads:
             for attachment in parsed_email["attachments"]:
                 if "payload" in attachment:
@@ -722,12 +794,14 @@ 

Source code for parsedmarc.utils

         parsed_email["subject"] = None
 
     parsed_email["filename_safe_subject"] = get_filename_safe_string(
-        parsed_email["subject"])
+        parsed_email["subject"]
+    )
 
     if "body" not in parsed_email:
         parsed_email["body"] = None
 
     return parsed_email
+
diff --git a/_sources/usage.md.txt b/_sources/usage.md.txt index e59f2c31..3bd0487c 100644 --- a/_sources/usage.md.txt +++ b/_sources/usage.md.txt @@ -103,6 +103,12 @@ port = 514 host = logger port = 12201 mode = tcp + +[webhook] +aggregate_url = https://aggregate_url.example.com +forensic_url = https://forensic_url.example.com +smtp_tls_url = https://smtp_tls_url.example.com +timeout = 60 ``` The full set of configuration options are: @@ -130,6 +136,8 @@ The full set of configuration options are: - `reverse_dns_map_url` - Overrides the default download URL for the reverse DNS map - `nameservers` - str: A comma separated list of DNS resolvers (Default: `[Cloudflare's public resolvers]`) + - `dns_test_address` - str: a dummy address used for DNS pre-flight checks + (Default: 1.1.1.1) - `dns_timeout` - float: DNS timeout period - `debug` - bool: Print debugging messages - `silent` - bool: Only print errors (Default: `True`) @@ -355,6 +363,16 @@ The full set of configuration options are: - `port` - int: The port to use - `mode` - str: The GELF transport type to use. Valid modes: `tcp`, `udp`, `tls` +- `maildir` + - `reports_folder` - str: Full path for mailbox maidir location (Default: `INBOX`) + - `maildir_create` - bool: Create maildir if not present (Default: False) + +- `webhook` - Post the individual reports to a webhook url with the report as the JSON body + - `aggregate_url` - str: URL of the webhook which should receive the aggregate reports + - `forensic_url` - str: URL of the webhook which should receive the forensic reports + - `smtp_tls_url` - str: URL of the webhook which should receive the smtp_tls reports + - `timeout` - int: Interval in which the webhook call should timeout + :::{warning} It is **strongly recommended** to **not** use the `nameservers` setting. By default, `parsedmarc` uses diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js index 8549469d..81415803 100644 --- a/_static/_sphinx_javascript_frameworks_compat.js +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -1,20 +1,9 @@ -/* - * _sphinx_javascript_frameworks_compat.js - * ~~~~~~~~~~ - * - * Compatability shim for jQuery and underscores.js. - * - * WILL BE REMOVED IN Sphinx 6.0 - * xref RemovedInSphinx60Warning +/* Compatability shim for jQuery and underscores.js. * + * Copyright Sphinx contributors + * Released under the two clause BSD licence */ -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - - /** * small helper function to urldecode strings * diff --git a/_static/basic.css b/_static/basic.css index eeb0519a..7ebbd6d0 100644 --- a/_static/basic.css +++ b/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { @@ -236,17 +225,11 @@ div.body p, div.body dd, div.body li, div.body blockquote { a.headerlink { visibility: hidden; } -a.brackets:before, -span.brackets > a:before{ - content: "["; -} -a.brackets:after, -span.brackets > a:after { - content: "]"; +a:visited { + color: #551A8B; } - h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, @@ -334,11 +317,17 @@ aside.sidebar { p.sidebar-title { font-weight: bold; } + +nav.contents, +aside.topic, div.admonition, div.topic, blockquote { clear: left; } /* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, div.topic { border: 1px solid #ccc; padding: 7px; @@ -377,6 +366,8 @@ div.body p.centered { div.sidebar > :last-child, aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, div.topic > :last-child, div.admonition > :last-child { margin-bottom: 0; @@ -384,6 +375,8 @@ div.admonition > :last-child { div.sidebar::after, aside.sidebar::after, +nav.contents::after, +aside.topic::after, div.topic::after, div.admonition::after, blockquote::after { @@ -608,19 +601,27 @@ ol.simple p, ul.simple p { margin-bottom: 0; } -dl.footnote > dt, -dl.citation > dt { + +aside.footnote > span, +div.citation > span { float: left; - margin-right: 0.5em; } - -dl.footnote > dd, -dl.citation > dd { +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { margin-bottom: 0em; } - -dl.footnote > dd:after, -dl.citation > dd:after { +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { content: ""; clear: both; } @@ -636,10 +637,6 @@ dl.field-list > dt { padding-left: 0.5em; padding-right: 5px; } -dl.field-list > dt:after { - content: ":"; -} - dl.field-list > dd { padding-left: 0.5em; @@ -666,6 +663,16 @@ dd { margin-left: 30px; } +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; @@ -734,6 +741,14 @@ abbr, acronym { cursor: help; } +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + /* -- code displays --------------------------------------------------------- */ pre { diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css index c718cee4..88ba55b9 100644 --- a/_static/css/badge_only.css +++ b/_static/css/badge_only.css @@ -1 +1 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/_static/css/theme.css b/_static/css/theme.css index 19a446a0..0f14f106 100644 --- a/_static/css/theme.css +++ b/_static/css/theme.css @@ -1,4 +1,4 @@ html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js index 527b876c..0398ebb9 100644 --- a/_static/doctools.js +++ b/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/_static/documentation_options.js b/_static/documentation_options.js index 58bbb4c2..39544038 100644 --- a/_static/documentation_options.js +++ b/_static/documentation_options.js @@ -1,6 +1,5 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '8.15.0', +const DOCUMENTATION_OPTIONS = { + VERSION: '8.15.1', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/_static/js/versions.js b/_static/js/versions.js new file mode 100644 index 00000000..818bc996 --- /dev/null +++ b/_static/js/versions.js @@ -0,0 +1,224 @@ +const themeFlyoutDisplay = "hidden"; +const themeVersionSelector = "True"; +const themeLanguageSelector = "True"; + +if (themeFlyoutDisplay === "attached") { + function renderLanguages(config) { + if (!config.projects.translations.length) { + return ""; + } + + const languagesHTML = ` +
+
Languages
+ ${config.projects.translations + .map( + (translation) => ` +
+ ${translation.language.code} +
+ `, + ) + .join("\n")} +
+ `; + return languagesHTML; + } + + function renderVersions(config) { + if (!config.versions.active.length) { + return ""; + } + const versionsHTML = ` +
+
Versions
+ ${config.versions.active + .map( + (version) => ` +
+ ${version.slug} +
+ `, + ) + .join("\n")} +
+ `; + return versionsHTML; + } + + function renderDownloads(config) { + if (!Object.keys(config.versions.current.downloads).length) { + return ""; + } + const downloadsNameDisplay = { + pdf: "PDF", + epub: "Epub", + htmlzip: "HTML", + }; + + const downloadsHTML = ` +
+
Downloads
+ ${Object.entries(config.versions.current.downloads) + .map( + ([name, url]) => ` +
+ ${downloadsNameDisplay[name]} +
+ `, + ) + .join("\n")} +
+ `; + return downloadsHTML; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const flyout = ` +
+ + Read the Docs + v: ${config.versions.current.slug} + + +
+
+ ${renderLanguages(config)} + ${renderVersions(config)} + ${renderDownloads(config)} +
+
On Read the Docs
+
+ Project Home +
+
+ Builds +
+
+ Downloads +
+
+
+
Search
+
+ + +
+ +
+
+ + Hosted by Read the Docs + +
+
+ `; + + // Inject the generated flyout into the body HTML element. + document.body.insertAdjacentHTML("beforeend", flyout); + + // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. + document + .querySelector("#flyout-search-form") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); + }) +} + +if (themeLanguageSelector || themeVersionSelector) { + function onSelectorSwitch(event) { + const option = event.target.selectedIndex; + const item = event.target.options[option]; + window.location.href = item.dataset.url; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const versionSwitch = document.querySelector( + "div.switch-menus > div.version-switch", + ); + if (themeVersionSelector) { + let versions = config.versions.active; + if (config.versions.current.hidden || config.versions.current.type === "external") { + versions.unshift(config.versions.current); + } + const versionSelect = ` + + `; + + versionSwitch.innerHTML = versionSelect; + versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + + const languageSwitch = document.querySelector( + "div.switch-menus > div.language-switch", + ); + + if (themeLanguageSelector) { + if (config.projects.translations.length) { + // Add the current language to the options on the selector + let languages = config.projects.translations.concat( + config.projects.current, + ); + languages = languages.sort((a, b) => + a.language.name.localeCompare(b.language.name), + ); + + const languageSelect = ` + + `; + + languageSwitch.innerHTML = languageSelect; + languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + else { + languageSwitch.remove(); + } + } + }); +} + +document.addEventListener("readthedocs-addons-data-ready", function (event) { + // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. + document + .querySelector("[role='search'] input") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); +}); \ No newline at end of file diff --git a/_static/language_data.js b/_static/language_data.js index 2e22b06a..c7fe6c6f 100644 --- a/_static/language_data.js +++ b/_static/language_data.js @@ -1,19 +1,12 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; -/* Non-minified version is copied as a separate JS file, is available */ +/* Non-minified version is copied as a separate JS file, if available */ /** * Porter Stemmer diff --git a/_static/searchtools.js b/_static/searchtools.js index e89e34d4..2c774d17 100644 --- a/_static/searchtools.js +++ b/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -57,16 +58,20 @@ const _removeChildren = (element) => { const _escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -const _displayItem = (item, searchTerms) => { +const _displayItem = (item, searchTerms, highlightTerms) => { const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -75,28 +80,35 @@ const _displayItem = (item, searchTerms) => { if (dirname.match(/\/index\/$/)) dirname = dirname.substring(0, dirname.length - 6); else if (dirname === "index/") dirname = ""; - requestUrl = docUrlRoot + dirname; + requestUrl = contentRoot + dirname; linkUrl = requestUrl; } else { // normal html builders - requestUrl = docUrlRoot + docName + docFileSuffix; + requestUrl = contentRoot + docName + docFileSuffix; linkUrl = docName + docLinkSuffix; } let linkEl = listItem.appendChild(document.createElement("a")); linkEl.href = linkUrl + anchor; linkEl.dataset.score = score; linkEl.innerHTML = title; - if (descr) + if (descr) { listItem.appendChild(document.createElement("span")).innerHTML = " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } else if (showSearchSummary) fetch(requestUrl) .then((responseData) => responseData.text()) .then((data) => { if (data) listItem.appendChild( - Search.makeSearchSummary(data, searchTerms) + Search.makeSearchSummary(data, searchTerms, anchor) ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); }); Search.output.appendChild(listItem); }; @@ -108,27 +120,46 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - `Search finished, found ${resultCount} page(s) matching the search query.` - ); + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, + ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( results, resultCount, - searchTerms + searchTerms, + highlightTerms, ) => { // results left, load the summary and display it // this is intended to be dynamic (don't sub resultsCount) if (results.length) { - _displayItem(results.pop(), searchTerms); + _displayItem(results.pop(), searchTerms, highlightTerms); setTimeout( - () => _displayNextItem(results, resultCount, searchTerms), + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), 5 ); } // search finished, update title and status message else _finishSearch(resultCount); }; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; /** * Default splitQuery function. Can be overridden in ``sphinx.search`` with a @@ -152,13 +183,26 @@ const Search = { _queued_query: null, _pulse_status: -1, - htmlToText: (htmlString) => { + htmlToText: (htmlString, anchor) => { const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent !== undefined) return docContent.textContent; + if (docContent) return docContent.textContent; + console.warn( - "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." ); return ""; }, @@ -211,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -231,16 +276,7 @@ const Search = { else Search.deferQuery(query); }, - /** - * execute search (requires search index to be loaded) - */ - query: (query) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - + _parseQuery: (query) => { // stem the search terms and add them to the correct list const stemmer = new Stemmer(); const searchTerms = new Set(); @@ -276,22 +312,40 @@ const Search = { // console.info("required: ", [...searchTerms]); // console.info("excluded: ", [...excludedTerms]); - // array of [docname, title, anchor, descr, score, filename] - let results = []; + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. + const normalResults = []; + const nonMainIndexResults = []; + _removeChildren(document.getElementById("search-progress")); - const queryLower = query.toLowerCase(); + const queryLower = query.toLowerCase().trim(); for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { for (const [file, id] of foundTitles) { - let score = Math.round(100 * queryLower.length / title.length) - results.push([ + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ docNames[file], titles[file] !== title ? `${titles[file]} > ${title}` : title, id !== null ? "#" + id : "", null, - score, + score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -300,46 +354,48 @@ const Search = { // search for explicit entries in index directives for (const [entry, foundEntries] of Object.entries(indexEntries)) { if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id] of foundEntries) { - let score = Math.round(100 * queryLower.length / entry.length) - results.push([ + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ docNames[file], titles[file], id ? "#" + id : "", null, score, filenames[file], - ]); + SearchResultKind.index, + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } } } } // lookup as object objectTerms.forEach((term) => - results.push(...Search.performObjectSearch(term, objectTerms)) + normalResults.push(...Search.performObjectSearch(term, objectTerms)) ); // lookup as search terms in fulltext - results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); // let the scorer override scores with a custom scoring function - if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); - - // now sort the results by score (in opposite order of appearance, since the - // display function below uses pop() to retrieve items) and then - // alphabetically - results.sort((a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; - }); + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; // remove duplicate search results // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept @@ -353,14 +409,19 @@ const Search = { return acc; }, []); - results = results.reverse(); + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); // for debugging //Search.lastresults = results.slice(); // a copy // console.info("search results:", Search.lastresults); // print the results - _displayNextItem(results, results.length, searchTerms); + _displayNextItem(results, results.length, searchTerms, highlightTerms); }, /** @@ -424,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -458,14 +520,18 @@ const Search = { // add support for partial matches if (word.length > 2) { const escapedWord = _escapeRegExp(word); - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord) && !terms[word]) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord) && !titleTerms[word]) - arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); - }); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } } // no match but word was a required one @@ -488,9 +554,8 @@ const Search = { // create the mapping files.forEach((file) => { - if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) - fileMap.get(file).push(word); - else fileMap.set(file, [word]); + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); }); }); @@ -531,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; @@ -541,8 +607,8 @@ const Search = { * search summary for a given text. keywords is a list * of stemmed words. */ - makeSearchSummary: (htmlText, keywords) => { - const text = Search.htmlToText(htmlText); + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); if (text === "") return null; const textLower = text.toLowerCase(); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js index aae669d7..8a96c69a 100644 --- a/_static/sphinx_highlight.js +++ b/_static/sphinx_highlight.js @@ -29,14 +29,19 @@ const _highlight = (node, addItems, text, className) => { } span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); parent.insertBefore( span, parent.insertBefore( - document.createTextNode(val.substr(pos + text.length)), + rest, node.nextSibling ) ); node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); if (isInSVG) { const rect = document.createElementNS( @@ -140,5 +145,10 @@ const SphinxHighlight = { }, }; -_ready(SphinxHighlight.highlightSearchWords); -_ready(SphinxHighlight.initEscapeListener); +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/api.html b/api.html index 2ac493c6..59df3a43 100644 --- a/api.html +++ b/api.html @@ -1,24 +1,21 @@ + + - + - + - API reference — parsedmarc 8.15.0 documentation - - + API reference — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -36,9 +33,6 @@ parsedmarc -
- 8.15.0 -
@@ -171,46 +165,46 @@
-

API reference

+

API reference

-

parsedmarc

+

parsedmarc

A Python package for parsing DMARC reports

-exception parsedmarc.InvalidAggregateReport[source]
+exception parsedmarc.InvalidAggregateReport[source]

Raised when an invalid DMARC aggregate report is encountered

-exception parsedmarc.InvalidDMARCReport[source]
+exception parsedmarc.InvalidDMARCReport[source]

Raised when an invalid DMARC report is encountered

-exception parsedmarc.InvalidForensicReport[source]
+exception parsedmarc.InvalidForensicReport[source]

Raised when an invalid DMARC forensic report is encountered

-exception parsedmarc.InvalidSMTPTLSReport[source]
+exception parsedmarc.InvalidSMTPTLSReport[source]

Raised when an invalid SMTP TLS report is encountered

-exception parsedmarc.ParserError[source]
+exception parsedmarc.ParserError[source]

Raised whenever the parser fails for some reason

-parsedmarc.email_results(results, host, mail_from, mail_to, mail_cc=None, mail_bcc=None, port=0, require_encryption=False, verify=True, username=None, password=None, subject=None, attachment_filename=None, message=None)[source]
+parsedmarc.email_results(results, host, mail_from, mail_to, mail_cc=None, mail_bcc=None, port=0, require_encryption=False, verify=True, username=None, password=None, subject=None, attachment_filename=None, message=None)[source]

Emails parsing results as a zip file

-
Parameters
+
Parameters:
  • results (OrderedDict) – Parsing results

  • host – Mail server hostname or IP address

  • @@ -233,20 +227,20 @@

    API reference
    -parsedmarc.extract_report(content)[source]
    +parsedmarc.extract_report(content)[source]

    Extracts text from a zip or gzip file, as a base64-encoded string, file-like object, or bytes.

    -
    Parameters
    +
    Parameters:
    • content – report file as a base64-encoded string, file-like object or

    • -
    • bytes.

    • +
    • bytes.

    -
    Returns
    +
    Returns:

    The extracted text

    -
    Return type
    +
    Return type:

    str

    @@ -254,16 +248,16 @@

    API reference
    -parsedmarc.extract_report_from_file_path(file_path)[source]
    +parsedmarc.extract_report_from_file_path(file_path)[source]

    Extracts report from a file at the given file_path

-parsedmarc.get_dmarc_reports_from_mailbox(connection: MailboxConnection, reports_folder='INBOX', archive_folder='Archive', delete=False, test=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, nameservers=None, dns_timeout=6.0, strip_attachment_payloads=False, results=None, batch_size=10, create_folders=True)[source]
+parsedmarc.get_dmarc_reports_from_mailbox(connection: MailboxConnection, reports_folder='INBOX', archive_folder='Archive', delete=False, test=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, nameservers=None, dns_timeout=6.0, strip_attachment_payloads=False, results=None, batch_size=10, create_folders=True)[source]

Fetches and parses DMARC reports from a mailbox

-
Parameters
+
Parameters:
@@ -297,11 +291,11 @@

API reference
-parsedmarc.get_dmarc_reports_from_mbox(input_, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False)[source]
+parsedmarc.get_dmarc_reports_from_mbox(input_, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False)[source]

Parses a mailbox in mbox format containing e-mails with attached DMARC reports

-
Parameters
+
Parameters:
@@ -327,16 +321,16 @@

API reference
-parsedmarc.get_report_zip(results)[source]
+parsedmarc.get_report_zip(results)[source]

Creates a zip file of parsed report output

-
Parameters
+
Parameters:

results (OrderedDict) – The parsed results

-
Returns
+
Returns:

zip file bytes

-
Return type
+
Return type:

bytes

@@ -344,11 +338,11 @@

API reference
-parsedmarc.parse_aggregate_report_file(_input, offline=False, always_use_local_files=None, reverse_dns_map_path=None, reverse_dns_map_url=None, ip_db_path=None, nameservers=None, dns_timeout=2.0, keep_alive=None)[source]
+parsedmarc.parse_aggregate_report_file(_input, offline=False, always_use_local_files=None, reverse_dns_map_path=None, reverse_dns_map_url=None, ip_db_path=None, nameservers=None, dns_timeout=2.0, keep_alive=None)[source]

Parses a file at the given path, a file-like object. or bytes as an aggregate DMARC report

-
Parameters
+
Parameters:
@@ -373,10 +367,10 @@

API reference
-parsedmarc.parse_aggregate_report_xml(xml, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, nameservers=None, timeout=2.0, keep_alive=None)[source]
+parsedmarc.parse_aggregate_report_xml(xml, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, nameservers=None, timeout=2.0, keep_alive=None)[source]

Parses a DMARC XML report string and returns a consistent OrderedDict

-
Parameters
+
Parameters:
@@ -401,10 +395,10 @@

API reference
-parsedmarc.parse_forensic_report(feedback_report, sample, msg_date, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, ip_db_path=None, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False)[source]
+parsedmarc.parse_forensic_report(feedback_report, sample, msg_date, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, ip_db_path=None, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False)[source]

Converts a DMARC forensic report and sample to a OrderedDict

-
Parameters
+
Parameters:
@@ -432,10 +426,10 @@

API reference
-parsedmarc.parse_report_email(input_, offline=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, keep_alive=None)[source]
+parsedmarc.parse_report_email(input_, offline=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, keep_alive=None)[source]

Parses a DMARC report from an email

-
Parameters
+
Parameters:
@@ -465,11 +459,11 @@

API reference
-parsedmarc.parse_report_file(input_, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, keep_alive=None)[source]
+parsedmarc.parse_report_file(input_, nameservers=None, dns_timeout=2.0, strip_attachment_payloads=False, ip_db_path=None, always_use_local_files=False, reverse_dns_map_path=None, reverse_dns_map_url=None, offline=False, keep_alive=None)[source]

Parses a DMARC aggregate or forensic file at the given path, a file-like object. or bytes

-
Parameters
+
Parameters:
@@ -496,23 +490,23 @@

API reference
-parsedmarc.parse_smtp_tls_report_json(report)[source]
+parsedmarc.parse_smtp_tls_report_json(report)[source]

Parses and validates an SMTP TLS report

-parsedmarc.parsed_aggregate_reports_to_csv(reports)[source]
+parsedmarc.parsed_aggregate_reports_to_csv(reports)[source]

Converts one or more parsed aggregate reports to flat CSV format, including headers

-
Parameters
+
Parameters:

reports – A parsed aggregate report or list of parsed aggregate reports

-
Returns
+
Returns:

Parsed aggregate report data in flat CSV format, including headers

-
Return type
+
Return type:

str

@@ -520,18 +514,18 @@

API reference
-parsedmarc.parsed_aggregate_reports_to_csv_rows(reports)[source]
+parsedmarc.parsed_aggregate_reports_to_csv_rows(reports)[source]

Converts one or more parsed aggregate reports to list of dicts in flat CSV format

-
Parameters
+
Parameters:

reports – A parsed aggregate report or list of parsed aggregate reports

-
Returns
+
Returns:

Parsed aggregate report data as a list of dicts in flat CSV format

-
Return type
+
Return type:

list

@@ -539,17 +533,17 @@

API reference
-parsedmarc.parsed_forensic_reports_to_csv(reports)[source]
+parsedmarc.parsed_forensic_reports_to_csv(reports)[source]

Converts one or more parsed forensic reports to flat CSV format, including headers

-
Parameters
+
Parameters:

reports – A parsed forensic report or list of parsed forensic reports

-
Returns
+
Returns:

Parsed forensic report data in flat CSV format, including headers

-
Return type
+
Return type:

str

@@ -557,17 +551,17 @@

API reference
-parsedmarc.parsed_forensic_reports_to_csv_rows(reports)[source]
+parsedmarc.parsed_forensic_reports_to_csv_rows(reports)[source]

Converts one or more parsed forensic reports to a list of dicts in flat CSV format

-
Parameters
+
Parameters:

reports – A parsed forensic report or list of parsed forensic reports

-
Returns
+
Returns:

Parsed forensic report data as a list of dicts in flat CSV format

-
Return type
+
Return type:

list

@@ -575,17 +569,17 @@

API reference
-parsedmarc.parsed_smtp_tls_reports_to_csv(reports)[source]
+parsedmarc.parsed_smtp_tls_reports_to_csv(reports)[source]

Converts one or more parsed SMTP TLS reports to flat CSV format, including headers

-
Parameters
+
Parameters:

reports – A parsed aggregate report or list of parsed aggregate reports

-
Returns
+
Returns:

Parsed aggregate report data in flat CSV format, including headers

-
Return type
+
Return type:

str

@@ -593,17 +587,17 @@

API reference
-parsedmarc.parsed_smtp_tls_reports_to_csv_rows(reports)[source]
+parsedmarc.parsed_smtp_tls_reports_to_csv_rows(reports)[source]

Converts one oor more parsed SMTP TLS reports into a list of single layer OrderedDict objects suitable for use in a CSV

-parsedmarc.save_output(results, output_directory='output', aggregate_json_filename='aggregate.json', forensic_json_filename='forensic.json', smtp_tls_json_filename='smtp_tls.json', aggregate_csv_filename='aggregate.csv', forensic_csv_filename='forensic.csv', smtp_tls_csv_filename='smtp_tls.csv')[source]
+parsedmarc.save_output(results, output_directory='output', aggregate_json_filename='aggregate.json', forensic_json_filename='forensic.json', smtp_tls_json_filename='smtp_tls.json', aggregate_csv_filename='aggregate.csv', forensic_csv_filename='forensic.csv', smtp_tls_csv_filename='smtp_tls.csv')[source]

Save report data in the given directory

-
Parameters
+
Parameters:
-

parsedmarc.utils

+

parsedmarc.utils

Utility functions that might be useful for other projects

-exception parsedmarc.utils.DownloadError[source]
+exception parsedmarc.utils.DownloadError[source]

Raised when an error occurs when downloading a file

-exception parsedmarc.utils.EmailParserError[source]
+exception parsedmarc.utils.EmailParserError[source]

Raised when an error parsing the email occurs

-parsedmarc.utils.convert_outlook_msg(msg_bytes)[source]
+parsedmarc.utils.convert_outlook_msg(msg_bytes)[source]

Uses the msgconvert Perl utility to convert an Outlook MS file to standard RFC 822 format

-
Parameters
+
Parameters:

msg_bytes (bytes) – the content of the .msg file

-
Returns
+
Returns:

A RFC 822 string

@@ -1003,16 +997,16 @@

API reference
-parsedmarc.utils.decode_base64(data)[source]
+parsedmarc.utils.decode_base64(data)[source]

Decodes a base64 string, with padding being optional

-
Parameters
+
Parameters:

data – A base64 encoded string

-
Returns
+
Returns:

The decoded bytes

-
Return type
+
Return type:

bytes

@@ -1020,7 +1014,7 @@

API reference
-parsedmarc.utils.get_base_domain(domain)[source]
+parsedmarc.utils.get_base_domain(domain)[source]

Gets the base domain name for the given domain

Note

@@ -1028,13 +1022,13 @@

API referencehttps://publicsuffix.org/list/public_suffix_list.dat.

-
Parameters
+
Parameters:

domain (str) – A domain or subdomain

-
Returns
+
Returns:

The base domain of the given domain

-
Return type
+
Return type:

str

@@ -1042,16 +1036,16 @@

API reference
-parsedmarc.utils.get_filename_safe_string(string)[source]
+parsedmarc.utils.get_filename_safe_string(string)[source]

Converts a string to a string that is safe for a filename

-
Parameters
+
Parameters:

string (str) – A string to make safe for a filename

-
Returns
+
Returns:

A string safe for a filename

-
Return type
+
Return type:

str

@@ -1059,20 +1053,20 @@

API reference
-parsedmarc.utils.get_ip_address_country(ip_address, db_path=None)[source]
+parsedmarc.utils.get_ip_address_country(ip_address, db_path=None)[source]

Returns the ISO code for the country associated with the given IPv4 or IPv6 address

-
Parameters
+
Parameters:
  • ip_address (str) – The IP address to query for

  • db_path (str) – Path to a MMDB file from MaxMind or DBIP

-
Returns
+
Returns:

And ISO country code associated with the given IP address

-
Return type
+
Return type:

str

@@ -1080,10 +1074,10 @@

API reference
-parsedmarc.utils.get_ip_address_info(ip_address, ip_db_path=None, reverse_dns_map_path=None, always_use_local_files=False, reverse_dns_map_url=None, cache=None, reverse_dns_map=None, offline=False, nameservers=None, timeout=2.0)[source]
+parsedmarc.utils.get_ip_address_info(ip_address, ip_db_path=None, reverse_dns_map_path=None, always_use_local_files=False, reverse_dns_map_url=None, cache=None, reverse_dns_map=None, offline=False, nameservers=None, timeout=2.0)[source]

Returns reverse DNS and country information for the given IP address

-
Parameters
+
Parameters:
@@ -1109,10 +1103,10 @@

API reference
-parsedmarc.utils.get_reverse_dns(ip_address, cache=None, nameservers=None, timeout=2.0)[source]
+parsedmarc.utils.get_reverse_dns(ip_address, cache=None, nameservers=None, timeout=2.0)[source]

Resolves an IP address to a hostname using a reverse DNS query

-
Parameters
+
Parameters:
@@ -1132,10 +1126,10 @@

API reference
-parsedmarc.utils.get_service_from_reverse_dns_base_domain(base_domain, always_use_local_file=False, local_file_path=None, url=None, offline=False, reverse_dns_map=None)[source]
+parsedmarc.utils.get_service_from_reverse_dns_base_domain(base_domain, always_use_local_file=False, local_file_path=None, url=None, offline=False, reverse_dns_map=None)[source]

Returns the service name of a given base domain name from reverse DNS.

-
Parameters
+
Parameters:
@@ -1158,19 +1152,19 @@

API reference
-parsedmarc.utils.human_timestamp_to_datetime(human_timestamp, to_utc=False)[source]
+parsedmarc.utils.human_timestamp_to_datetime(human_timestamp, to_utc=False)[source]

Converts a human-readable timestamp into a Python datetime object

-
Parameters
+
Parameters:
  • human_timestamp (str) – A timestamp string

  • to_utc (bool) – Convert the timestamp to UTC

-
Returns
+
Returns:

The converted timestamp

-
Return type
+
Return type:

datetime

@@ -1178,16 +1172,16 @@

API reference
-parsedmarc.utils.human_timestamp_to_unix_timestamp(human_timestamp)[source]
+parsedmarc.utils.human_timestamp_to_unix_timestamp(human_timestamp)[source]

Converts a human-readable timestamp into a UNIX timestamp

-
Parameters
+
Parameters:

human_timestamp (str) – A timestamp in YYYY-MM-DD HH:MM:SS` format

-
Returns
+
Returns:

The converted timestamp

-
Return type
+
Return type:

float

@@ -1195,16 +1189,16 @@

API reference
-parsedmarc.utils.is_mbox(path)[source]
+parsedmarc.utils.is_mbox(path)[source]

Checks if the given content is an MBOX mailbox file

-
Parameters
+
Parameters:

path – Content to check

-
Returns
+
Returns:

A flag that indicates if the file is an MBOX mailbox file

-
Return type
+
Return type:

bool

@@ -1212,16 +1206,16 @@

API reference
-parsedmarc.utils.is_outlook_msg(content)[source]
+parsedmarc.utils.is_outlook_msg(content)[source]

Checks if the given content is an Outlook msg OLE/MSG file

-
Parameters
+
Parameters:

content – Content to check

-
Returns
+
Returns:

A flag that indicates if the file is an Outlook MSG file

-
Return type
+
Return type:

bool

@@ -1229,19 +1223,19 @@

API reference
-parsedmarc.utils.parse_email(data, strip_attachment_payloads=False)[source]
+parsedmarc.utils.parse_email(data, strip_attachment_payloads=False)[source]

A simplified email parser

-
Parameters
+
Parameters:
  • data – The RFC 822 message string, or MSG binary

  • strip_attachment_payloads (bool) – Remove attachment payloads

-
Returns
+
Returns:

Parsed email data

-
Return type
+
Return type:

dict

@@ -1249,10 +1243,10 @@

API reference
-parsedmarc.utils.query_dns(domain, record_type, cache=None, nameservers=None, timeout=2.0)[source]
+parsedmarc.utils.query_dns(domain, record_type, cache=None, nameservers=None, timeout=2.0)[source]

Queries DNS

-
Parameters
+
Parameters:
@@ -1273,16 +1267,16 @@

API reference
-parsedmarc.utils.timestamp_to_datetime(timestamp)[source]
+parsedmarc.utils.timestamp_to_datetime(timestamp)[source]

Converts a UNIX/DMARC timestamp to a Python datetime object

-
Parameters
+
Parameters:

timestamp (int) – The timestamp

-
Returns
+
Returns:

The converted timestamp as a Python datetime object

-
Return type
+
Return type:

datetime

@@ -1290,16 +1284,16 @@

API reference
-parsedmarc.utils.timestamp_to_human(timestamp)[source]
+parsedmarc.utils.timestamp_to_human(timestamp)[source]

Converts a UNIX/DMARC timestamp to a human-readable string

-
Parameters
+
Parameters:

timestamp – The timestamp

-
Returns
+
Returns:

The converted timestamp in YYYY-MM-DD HH:MM:SS format

-
Return type
+
Return type:

str

@@ -1307,7 +1301,7 @@

API reference -

Indices and tables

+

Indices and tables

  • Index

  • Module Index

  • diff --git a/contributing.html b/contributing.html index 6df25655..692a5d46 100644 --- a/contributing.html +++ b/contributing.html @@ -1,24 +1,21 @@ + + - + - + - Contributing to parsedmarc — parsedmarc 8.15.0 documentation - - + Contributing to parsedmarc — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -91,9 +85,9 @@
    -

    Contributing to parsedmarc

    +

    Contributing to parsedmarc

    -

    Bug reports

    +

    Bug reports

    Please report bugs on the GitHub issue tracker

    https://github.com/domainaware/parsedmarc/issues

    diff --git a/davmail.html b/davmail.html index 1ea39354..bb9c3cbe 100644 --- a/davmail.html +++ b/davmail.html @@ -1,24 +1,21 @@ + + - + - + - Accessing an inbox using OWA/EWS — parsedmarc 8.15.0 documentation - - + Accessing an inbox using OWA/EWS — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -92,7 +86,7 @@
    -

    Accessing an inbox using OWA/EWS

    +

    Accessing an inbox using OWA/EWS

    Note

    Starting in 8.0.0, parsedmarc supports accessing Microsoft/Office 365 @@ -177,7 +171,7 @@

    Accessing an inbox using OWA/EWS -

    Running DavMail as a systemd service

    +

    Running DavMail as a systemd service

    Use systemd to run davmail as a service.

    Create a system user

    sudo useradd davmail -r -s /bin/false
    @@ -244,7 +238,7 @@ 

    Running DavMail as a systemd service -

    Configuring parsedmarc for DavMail

    +

    Configuring parsedmarc for DavMail

    Because you are interacting with DavMail server over the loopback (i.e. 127.0.0.1), add the following options to parsedmarc.ini config file:

    diff --git a/dmarc.html b/dmarc.html index 3a76485c..29c55592 100644 --- a/dmarc.html +++ b/dmarc.html @@ -1,24 +1,21 @@ + + - + - + - Understanding DMARC — parsedmarc 8.15.0 documentation - - + Understanding DMARC — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -110,39 +104,34 @@
    -

    Understanding DMARC

    +

    Understanding DMARC

    -

    Resources

    +

    Resources

    -

    DMARC guides

    +

    DMARC guides

    -

    SPF and DMARC record validation

    +

    SPF and DMARC record validation

    If you are looking for SPF and DMARC record validation and parsing, check out the sister project, checkdmarc.

    -

    Lookalike domains

    +

    Lookalike domains

    DMARC protects against domain spoofing, not lookalike domains. for open source lookalike domain monitoring, check out DomainAware.

    -

    DMARC Alignment Guide

    +

    DMARC Alignment Guide

    DMARC ensures that SPF and DKM authentication mechanisms actually authenticate against the same domain that the end user sees.

    A message passes a DMARC check by passing DKIM or SPF, as long as the related indicators are also in alignment.

    ----- @@ -180,7 +169,7 @@

    DMARC Alignment Guide
    -

    What if a sender won’t support DKIM/DMARC?

    +

    What if a sender won’t support DKIM/DMARC?

    1. Some vendors don’t know about DMARC yet; ask about SPF and DKIM/email authentication.

    2. @@ -200,21 +189,21 @@

      What if a sender won’t support DKIM/DMARC? -

      What about mailing lists?

      +

      What about mailing lists?

      When you deploy DMARC on your domain, you might find that messages relayed by mailing lists are failing DMARC, most likely because the mailing list is spoofing your from address, and modifying the subject, footer, or other part of the message, thereby breaking the DKIM signature.

      -

      Mailing list best practices

      +

      Mailing list best practices

      Ideally, a mailing list should forward messages without altering the headers or body content at all. Joe Nelson does a fantastic job of explaining exactly what mailing lists should and shouldn’t do to be fully DMARC compliant. Rather than repeat his fine work, here’s a summary:

      -

      Do

      +

      Do

      • Retain headers from the original message

      • Add RFC 2369 List-Unsubscribe headers to outgoing messages, instead of @@ -234,7 +223,7 @@

        Do these headers.

      -

      Do not

      +

      Do not

      -

      Mailman 2

      +

      Mailman 2

      Navigate to General Settings, and configure the settings below

    DKIM

    ---- @@ -287,10 +272,6 @@

    Mailman 2 -

    --- @@ -308,10 +289,6 @@

    Mailman 2 -

    --- @@ -329,16 +306,12 @@

    Mailman 2 -

    Mailman 3

    +

    Mailman 3

    Navigate to Settings> List Identity

    Make Subject prefix blank.

    Navigate to Settings> Alter Messages

    Configure the settings below

    Setting

    Value

    Setting

    Value

    Setting

    Value

    ---- @@ -366,10 +339,6 @@

    Mailman 3 -

    --- @@ -393,13 +362,13 @@

    Mailman 3 -

    LISTSERV

    +

    LISTSERV

    LISTSERV 16.0-2017a and higher will rewrite the From header for domains that enforce with a DMARC quarantine or reject policy.

    Some additional steps are needed for Linux hosts.

    -

    Workarounds

    +

    Workarounds

    If a mailing list must go against best practices and modify the message (e.g. to add a required legal footer), the mailing list administrator must configure the list to replace the From address of the @@ -407,13 +376,9 @@

    Workarounds -
    Mailman 2
    +

    Mailman 2

    Navigate to Privacy Options> Sending Filters, and configure the settings below

    Setting

    Value

    Setting

    Value

    ---- @@ -442,13 +407,9 @@
    Mailman 2
    -
    Mailman 3
    +
    Mailman 3

    In the DMARC Mitigations tab of the Settings page, configure the settings below

    Setting

    Value

    ---- diff --git a/elasticsearch.html b/elasticsearch.html index bcbfc5f3..e3fdaecf 100644 --- a/elasticsearch.html +++ b/elasticsearch.html @@ -1,24 +1,21 @@ + + - + - + - Elasticsearch and Kibana — parsedmarc 8.15.0 documentation - - + Elasticsearch and Kibana — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -93,14 +87,14 @@
    -

    Elasticsearch and Kibana

    +

    Elasticsearch and Kibana

    To set up visual dashboards of DMARC data, install Elasticsearch and Kibana.

    Note

    Elasticsearch and Kibana 6 or later are required

    -

    Installation

    +

    Installation

    On Debian/Ubuntu based systems, run:

    sudo apt-get install -y apt-transport-https
     wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
    @@ -243,11 +237,13 @@ 

    InstallationX-Pack.

    -A screenshot of setting the Saved Objects Stack management UI in Kibana -A screenshot of the overwrite conformation prompt +A screenshot of setting the Saved Objects Stack management UI in Kibana + +A screenshot of the overwrite conformation prompt +

    -

    Upgrading Kibana index patterns

    +

    Upgrading Kibana index patterns

    parsedmarc 5.0.0 makes some changes to the way data is indexed in Elasticsearch. if you are upgrading from a previous release of parsedmarc, you need to complete the following steps to replace the @@ -266,7 +262,7 @@

    Upgrading Kibana index patterns -

    Records retention

    +

    Records retention

    Starting in version 5.0.0, parsedmarc stores data in a separate index for each day to make it easy to comply with records retention regulations such as GDPR. For more information, diff --git a/genindex.html b/genindex.html index 712a0e56..5fe4aedf 100644 --- a/genindex.html +++ b/genindex.html @@ -1,23 +1,20 @@ + + - + - Index — parsedmarc 8.15.0 documentation - - + Index — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -34,9 +31,6 @@ parsedmarc -

    - 8.15.0 -
    diff --git a/index.html b/index.html index e16a0791..17b97f92 100644 --- a/index.html +++ b/index.html @@ -1,24 +1,21 @@ + + - + - + - parsedmarc documentation - Open source DMARC report analyzer and visualizer — parsedmarc 8.15.0 documentation - - + parsedmarc documentation - Open source DMARC report analyzer and visualizer — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -36,9 +33,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -87,7 +81,7 @@
    -

    parsedmarc documentation - Open source DMARC report analyzer and visualizer

    +

    parsedmarc documentation - Open source DMARC report analyzer and visualizer

    BuildStatus CodeCoverage PyPIPackage @@ -100,14 +94,15 @@

    parsedmarc documentation - Open source DMARC report analyzer and visualizer< Assistance on the pinned issues would be particularly helpful.

    Thanks to all contributors!

    -A screenshot of DMARC summary charts in Kibana +A screenshot of DMARC summary charts in Kibana +

    parsedmarc is a Python module and CLI utility for parsing DMARC reports. When used with Elasticsearch and Kibana (or Splunk), or with OpenSearch and Grafana, it works as a self-hosted open source alternative to commercial DMARC report processing services such as Agari Brand Protection, Dmarcian, OnDMARC, ProofPoint Email Fraud Defense, and Valimail.

    -

    Features

    +

    Features

    • Parses draft and 1.0 standard aggregate/rua reports

    • Parses forensic/failure/ruf reports

    • diff --git a/installation.html b/installation.html index 15829248..4df49f00 100644 --- a/installation.html +++ b/installation.html @@ -1,24 +1,21 @@ + + - + - + - Installation — parsedmarc 8.15.0 documentation - - + Installation — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
      - 8.15.0 -
      @@ -99,18 +93,18 @@
      -

      Installation

      +

      Installation

      -

      Prerequisites

      +

      Prerequisites

      parsedmarc works with Python 3 only.

      -

      Testing multiple report analyzers

      +

      Testing multiple report analyzers

      If you would like to test parsedmarc and another report processing solution at the same time, you can have up to two mailto URIs in each of the rua and ruf tags in your DMARC record, separated by commas.

      -

      Using a web proxy

      +

      Using a web proxy

      If your system is behind a web proxy, you need to configure your system to use that proxy. To do this, edit /etc/environment and add your proxy details there, for example:

      @@ -128,7 +122,7 @@

      Using a web proxyparsedmarc.

      -

      Using Microsoft Exchange

      +

      Using Microsoft Exchange

      If your mail server is Microsoft Exchange, ensure that it is patched to at least:

        @@ -138,7 +132,7 @@

        Using Microsoft Exchange -

        geoipupdate setup

        +

        geoipupdate setup

        Note

        Starting in parsedmarc 7.1.0, a static copy of the @@ -210,7 +204,7 @@

        geoipupdate setup -

        Installing parsedmarc

        +

        Installing parsedmarc

        On Debian or Ubuntu systems, run:

        sudo apt-get install -y python3-pip python3-virtualenv python3-dev libxml2-dev libxslt-dev
         
        @@ -245,7 +239,7 @@

        Installing parsedmarc

      -

      Optional dependencies

      +

      Optional dependencies

      If you would like to be able to parse emails saved from Microsoft Outlook (i.e. OLE .msg files), install msgconvert:

      On Debian or Ubuntu systems, run:

      diff --git a/kibana.html b/kibana.html index 1cf8d575..d3069d20 100644 --- a/kibana.html +++ b/kibana.html @@ -1,24 +1,21 @@ + + - + - + - Using the Kibana dashboards — parsedmarc 8.15.0 documentation - - + Using the Kibana dashboards — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
      - 8.15.0 -
      @@ -92,7 +86,7 @@
      -

      Using the Kibana dashboards

      +

      Using the Kibana dashboards

      The Kibana DMARC dashboards are a human-friendly way to understand the results from incoming DMARC reports.

      @@ -101,7 +95,7 @@

      Using the Kibana dashboards -

      DMARC Summary

      +

      DMARC Summary

      As the name suggests, this dashboard is the best place to start reviewing your aggregate DMARC data.

      Across the top of the dashboard, three pie charts display the percentage of @@ -158,7 +152,7 @@

      DMARC Summary -

      DMARC Forensic Samples

      +

      DMARC Forensic Samples

      The DMARC Forensic Samples dashboard contains information on DMARC forensic reports (also known as failure reports or ruf reports). These reports contain samples of emails that have failed to pass DMARC.

      diff --git a/mailing-lists.html b/mailing-lists.html index 25e659e8..565bc952 100644 --- a/mailing-lists.html +++ b/mailing-lists.html @@ -1,24 +1,21 @@ + + - + - + - What about mailing lists? — parsedmarc 8.15.0 documentation - - + What about mailing lists? — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -35,9 +32,6 @@ parsedmarc -
      - 8.15.0 -
      @@ -86,21 +80,21 @@
      -

      What about mailing lists?

      +

      What about mailing lists?

      When you deploy DMARC on your domain, you might find that messages relayed by mailing lists are failing DMARC, most likely because the mailing list is spoofing your from address, and modifying the subject, footer, or other part of the message, thereby breaking the DKIM signature.

      -

      Mailing list best practices

      +

      Mailing list best practices

      Ideally, a mailing list should forward messages without altering the headers or body content at all. Joe Nelson does a fantastic job of explaining exactly what mailing lists should and shouldn’t do to be fully DMARC compliant. Rather than repeat his fine work, here’s a summary:

      -

      Do

      +

      Do

      • Retain headers from the original message

      • Add RFC 2369 List-Unsubscribe headers to outgoing messages, instead of @@ -120,7 +114,7 @@

        Do these headers.

      -

      Do not

      +

      Do not

      -

      Mailman 2

      +

      Mailman 2

      Navigate to General Settings, and configure the settings below

    Setting

    Value

    ---- @@ -173,10 +163,6 @@

    Mailman 2 -

    --- @@ -194,10 +180,6 @@

    Mailman 2 -

    --- @@ -215,16 +197,12 @@

    Mailman 2 -

    Mailman 3

    +

    Mailman 3

    Navigate to Settings> List Identity

    Make Subject prefix blank.

    Navigate to Settings> Alter Messages

    Configure the settings below

    Setting

    Value

    Setting

    Value

    Setting

    Value

    ---- @@ -252,10 +230,6 @@

    Mailman 3 -

    --- @@ -279,13 +253,13 @@

    Mailman 3 -

    LISTSERV

    +

    LISTSERV

    LISTSERV 16.0-2017a and higher will rewrite the From header for domains that enforce with a DMARC quarantine or reject policy.

    Some additional steps are needed for Linux hosts.

    -

    Workarounds

    +

    Workarounds

    If a mailing list must go against best practices and modify the message (e.g. to add a required legal footer), the mailing list administrator must configure the list to replace the From address of the @@ -293,13 +267,9 @@

    Workarounds -

    Mailman 2

    +

    Mailman 2

    Navigate to Privacy Options> Sending Filters, and configure the settings below

    Setting

    Value

    Setting

    Value

    ---- @@ -328,13 +298,9 @@

    Mailman 2
    -

    Mailman 3

    +

    Mailman 3

    In the DMARC Mitigations tab of the Settings page, configure the settings below

    Setting

    Value

    ---- diff --git a/objects.inv b/objects.inv index 06b9d23de49001c613efcb430f2452b5a31dc553..ce07f1eaab08926df418d66df74153652a161219 100644 GIT binary patch delta 12 Tcmcc2ahYR+Go#@~7k(B19nb^B delta 12 Tcmcc2ahYR+Go!&q7k(B19m@m5 diff --git a/opensearch.html b/opensearch.html index 093387a9..65eeed27 100644 --- a/opensearch.html +++ b/opensearch.html @@ -1,24 +1,21 @@ + + - + - + - OpenSearch and Grafana — parsedmarc 8.15.0 documentation - - + OpenSearch and Grafana — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -92,15 +86,15 @@
    -

    OpenSearch and Grafana

    +

    OpenSearch and Grafana

    To set up visual dashboards of DMARC data, install OpenSearch and Grafana.

    -

    Installation

    +

    Installation

    OpenSearch: https://opensearch.org/docs/latest/install-and-configure/install-opensearch/index/ Grafana: https://grafana.com/docs/grafana/latest/setup-grafana/installation/

    -

    Records retention

    +

    Records retention

    Starting in version 5.0.0, parsedmarc stores data in a separate index for each day to make it easy to comply with records retention regulations such as GDPR.

    diff --git a/output.html b/output.html index 0eec8055..0a504a73 100644 --- a/output.html +++ b/output.html @@ -1,24 +1,21 @@ + + - + - + - Sample outputs — parsedmarc 8.15.0 documentation - - + Sample outputs — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -101,9 +95,9 @@
    -

    Sample outputs

    +

    Sample outputs

    -

    Sample aggregate report output

    +

    Sample aggregate report output

    Here are the results from parsing the example report from the dmarc.org wiki. It’s actually an older draft of the 1.0 report schema standardized in @@ -112,7 +106,7 @@

    Sample aggregate report outputparsedmarc produces consistent, normalized output, regardless of the report schema.

    -

    JSON aggregate report

    +

    JSON aggregate report

    {
       "xml_schema": "draft",
       "report_metadata": {
    @@ -181,7 +175,7 @@ 

    JSON aggregate report

    -

    CSV aggregate report

    +

    CSV aggregate report

    xml_schema,org_name,org_email,org_extra_contact_info,report_id,begin_date,end_date,errors,domain,adkim,aspf,p,sp,pct,fo,source_ip_address,source_country,source_reverse_dns,source_base_domain,count,spf_aligned,dkim_aligned,dmarc_aligned,disposition,policy_override_reasons,policy_override_comments,envelope_from,header_from,envelope_to,dkim_domains,dkim_selectors,dkim_results,spf_domains,spf_scopes,spf_results
     draft,acme.com,noreply-dmarc-support@acme.com,http://acme.com/dmarc/support,9391651994964116463,2012-04-27 20:00:00,2012-04-28 19:59:59,,example.com,r,r,none,none,100,0,72.150.241.94,US,adsl-72-150-241-94.shv.bellsouth.net,bellsouth.net,2,True,False,True,none,,,example.com,example.com,,example.com,none,fail,example.com,mfrom,pass
     
    @@ -189,11 +183,11 @@

    CSV aggregate report

    -

    Sample forensic report output

    +

    Sample forensic report output

    Thanks to GitHub user xennn for the anonymized forensic report email sample.

    -

    JSON forensic report

    +

    JSON forensic report

    {
          "feedback_type": "auth-failure",
          "user_agent": "Lua/1.0",
    @@ -282,14 +276,14 @@ 

    JSON forensic report

    -

    CSV forensic report

    +

    CSV forensic report

    feedback_type,user_agent,version,original_envelope_id,original_mail_from,original_rcpt_to,arrival_date,arrival_date_utc,subject,message_id,authentication_results,dkim_domain,source_ip_address,source_country,source_reverse_dns,source_base_domain,delivery_result,auth_failure,reported_domain,authentication_mechanisms,sample_headers_only
     auth-failure,Lua/1.0,1.0,,sharepoint@domain.de,peter.pan@domain.de,"Mon, 01 Oct 2018 11:20:27 +0200",2018-10-01 09:20:27,Subject,<38.E7.30937.BD6E1BB5@ mailrelay.de>,"dmarc=fail (p=none, dis=none) header.from=domain.de",,10.10.10.10,,,,policy,dmarc,domain.de,,False
     
    -

    JSON SMTP TLS report

    +

    JSON SMTP TLS report

    [
       {
         "organization_name": "Example Inc.",
    diff --git a/py-modindex.html b/py-modindex.html
    index ad9bbc7b..3cc5c80f 100644
    --- a/py-modindex.html
    +++ b/py-modindex.html
    @@ -1,23 +1,20 @@
    +
    +
     
    -
    +
     
       
       
    -  Python Module Index — parsedmarc 8.15.0 documentation
    -      
    -      
    +  Python Module Index — parsedmarc 8.15.1 documentation
    +      
    +      
     
       
    -  
    -  
    -        
    -        
    -        
    -        
    -        
    -        
    +      
    +      
    +      
    +      
    +      
         
         
         
    @@ -37,9 +34,6 @@
               
                 parsedmarc
               
    -              
    - 8.15.0 -
    diff --git a/search.html b/search.html index 2a8acb83..c664bfb8 100644 --- a/search.html +++ b/search.html @@ -1,24 +1,21 @@ + + - + - Search — parsedmarc 8.15.0 documentation - - + Search — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    diff --git a/searchindex.js b/searchindex.js index bb667b13..d82e8063 100644 --- a/searchindex.js +++ b/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["api", "contributing", "davmail", "dmarc", "elasticsearch", "index", "installation", "kibana", "mailing-lists", "opensearch", "output", "splunk", "usage"], "filenames": ["api.md", "contributing.md", "davmail.md", "dmarc.md", "elasticsearch.md", "index.md", "installation.md", "kibana.md", "mailing-lists.md", "opensearch.md", "output.md", "splunk.md", "usage.md"], "titles": ["API reference", "Contributing to parsedmarc", "Accessing an inbox using OWA/EWS", "Understanding DMARC", "Elasticsearch and Kibana", "parsedmarc documentation - Open source DMARC report analyzer and visualizer", "Installation", "Using the Kibana dashboards", "What about mailing lists?", "OpenSearch and Grafana", "Sample outputs", "Splunk", "Using parsedmarc"], "terms": {"A": [0, 3, 12], "python": [0, 5, 6], "packag": [0, 4], "pars": [0, 3, 5, 6, 10, 12], "dmarc": [0, 4, 6, 8, 9, 10, 11, 12], "report": [0, 4, 7, 11, 12], "except": [0, 12], "invalidaggregatereport": 0, "sourc": [0, 3, 4, 6, 7, 10], "rais": 0, "when": [0, 3, 5, 7, 8, 12], "an": [0, 3, 5, 7, 8, 10, 12], "invalid": 0, "aggreg": [0, 5, 7, 11, 12], "i": [0, 2, 3, 4, 5, 6, 7, 8, 10, 12], "encount": 0, "invaliddmarcreport": 0, "invalidforensicreport": 0, "forens": [0, 5, 11, 12], "invalidsmtptlsreport": 0, "smtp": [0, 3, 7, 12], "tl": [0, 12], "parsererror": 0, "whenev": [0, 2, 12], "parser": 0, "fail": [0, 3, 7, 8, 10, 12], "some": [0, 2, 3, 4, 7, 8], "reason": [0, 2, 4, 12], "email_result": 0, "result": [0, 5, 7, 10, 12], "host": [0, 2, 3, 4, 5, 8, 12], "mail_from": 0, "mail_to": 0, "mail_cc": 0, "none": [0, 3, 10, 12], "mail_bcc": 0, "port": [0, 2, 12], "0": [0, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12], "require_encrypt": 0, "fals": [0, 2, 6, 10, 12], "verifi": 0, "true": [0, 2, 4, 10, 12], "usernam": [0, 12], "password": [0, 4, 6, 12], "subject": [0, 3, 8, 10, 12], "attachment_filenam": 0, "messag": [0, 2, 3, 4, 6, 7, 8, 10, 12], "email": [0, 3, 5, 6, 7, 8, 10, 11, 12], "zip": [0, 2, 5, 12], "file": [0, 2, 5, 6, 11], "paramet": 0, "ordereddict": 0, "mail": [0, 5, 6, 10, 12], "server": [0, 2, 3, 4, 6, 7, 10, 12], "hostnam": [0, 12], "ip": [0, 3, 4, 6, 7, 12], "address": [0, 2, 3, 4, 7, 8, 10, 12], "The": [0, 3, 6, 7, 11, 12], "valu": [0, 3, 4, 7, 8, 12], "from": [0, 2, 3, 4, 5, 6, 7, 8, 10, 12], "header": [0, 3, 7, 8, 10, 12], "list": [0, 2, 4, 5, 7, 12], "cc": [0, 10], "bcc": [0, 10], "int": [0, 12], "us": [0, 3, 4, 5, 8, 10], "bool": [0, 12], "requir": [0, 2, 3, 4, 6, 8, 12], "secur": [0, 4, 12], "connect": [0, 2, 4, 12], "start": [0, 2, 4, 6, 7, 9, 11, 12], "ssl": [0, 2, 4, 12], "certif": [0, 4, 12], "str": [0, 12], "option": [0, 2, 3, 4, 5, 8, 11, 12], "overrid": [0, 12], "default": [0, 2, 4, 6, 7, 12], "attach": [0, 3, 8, 10, 12], "filenam": [0, 12], "plain": 0, "text": [0, 10], "bodi": [0, 3, 8, 10, 12], "extract_report": 0, "content": [0, 3, 8, 10, 11], "extract": [0, 2], "gzip": [0, 5], "base64": 0, "encod": [0, 10, 12], "string": 0, "like": [0, 3, 6, 8], "object": [0, 4], "byte": 0, "return": 0, "type": [0, 10, 12], "extract_report_from_file_path": 0, "file_path": [0, 12], "given": [0, 12], "get_dmarc_reports_from_mailbox": 0, "mailboxconnect": 0, "reports_fold": [0, 12], "inbox": [0, 3, 5, 8, 12], "archive_fold": [0, 12], "archiv": [0, 12], "delet": [0, 2, 4, 12], "test": [0, 10, 12], "ip_db_path": [0, 6, 12], "always_use_local_fil": [0, 12], "reverse_dns_map_path": 0, "reverse_dns_map_url": [0, 12], "offlin": [0, 12], "nameserv": [0, 12], "dns_timeout": [0, 12], "6": [0, 4, 6, 12], "strip_attachment_payload": [0, 12], "batch_siz": [0, 12], "10": [0, 6, 10, 12], "create_fold": 0, "fetch": [0, 12], "mailbox": [0, 7, 12], "folder": [0, 2, 12], "where": [0, 2, 3, 8, 12], "can": [0, 2, 3, 4, 5, 6, 7, 8, 12], "found": [0, 6, 12], "move": [0, 4, 12], "process": [0, 2, 5, 6, 12], "after": [0, 2, 4, 12], "them": [0, 4, 7, 12], "do": [0, 2, 6, 7, 12], "path": [0, 4, 12], "mmdb": [0, 12], "maxmind": [0, 6, 12], "dbip": [0, 12], "download": [0, 2, 4, 6, 12], "revers": [0, 7, 12], "dn": [0, 3, 7, 12], "map": [0, 12], "url": [0, 2, 12], "queri": [0, 12], "onlin": [0, 2, 12], "geoloc": [0, 12], "float": [0, 12], "set": [0, 2, 3, 4, 6, 7, 8, 9, 12], "timeout": [0, 2, 12], "remov": [0, 3, 4, 8, 12], "payload": [0, 12], "dict": 0, "previou": [0, 2, 4, 12], "run": [0, 4, 5, 6], "number": [0, 12], "read": [0, 12], "befor": [0, 12], "save": [0, 4, 6, 12], "limit": [0, 2, 12], "whether": 0, "creat": [0, 2, 3, 4, 6, 8, 12], "destin": 0, "watch": [0, 2, 4, 12], "aggregate_report": 0, "forensic_report": 0, "get_dmarc_reports_from_mbox": 0, "input_": 0, "2": [0, 4, 10, 12], "mbox": [0, 12], "format": [0, 6], "contain": [0, 7, 11, 12], "e": [0, 2, 3, 4, 6, 8, 12], "input": 0, "one": [0, 3, 5, 8, 12], "more": [0, 4, 6, 11, 12], "cloudflar": [0, 12], "": [0, 2, 3, 4, 6, 8, 10, 12], "public": [0, 3, 10, 12], "resolv": [0, 12], "second": [0, 2, 12], "make": [0, 3, 4, 8, 9, 12], "get_report_zip": 0, "output": [0, 5, 12], "parse_aggregate_report_fil": 0, "_input": 0, "keep_al": 0, "callabl": 0, "keep": 0, "aliv": 0, "function": 0, "parse_aggregate_report_xml": 0, "xml": [0, 11], "consist": [0, 5, 10], "parse_forensic_report": 0, "feedback_report": 0, "sampl": [0, 5, 12], "msg_date": 0, "convert": [0, 3, 8], "feedback": 0, "rfc": [0, 3, 8, 10], "822": 0, "date": [0, 3, 8, 10], "parse_report_email": 0, "report_typ": 0, "parse_report_fil": 0, "parse_smtp_tls_report_json": 0, "valid": [0, 7, 10, 12], "parsed_aggregate_reports_to_csv": 0, "flat": 0, "csv": [0, 5, 12], "includ": [0, 3, 6, 7, 8, 12], "data": [0, 4, 5, 7, 9, 11, 12], "parsed_aggregate_reports_to_csv_row": 0, "parsed_forensic_reports_to_csv": 0, "parsed_forensic_reports_to_csv_row": 0, "parsed_smtp_tls_reports_to_csv": 0, "parsed_smtp_tls_reports_to_csv_row": 0, "oor": 0, "singl": 0, "layer": 0, "suitabl": 0, "save_output": 0, "output_directori": 0, "aggregate_json_filenam": [0, 12], "json": [0, 5, 12], "forensic_json_filenam": [0, 12], "smtp_tls_json_filenam": 0, "smtp_tl": 0, "aggregate_csv_filenam": [0, 12], "forensic_csv_filenam": [0, 12], "smtp_tls_csv_filenam": 0, "directori": [0, 12], "watch_inbox": 0, "mailbox_connect": 0, "callback": 0, "check_timeout": [0, 12], "30": [0, 12], "new": [0, 2, 3, 6, 7, 12], "send": [0, 2, 3, 4, 5, 7, 8, 11, 12], "receiv": [0, 10, 12], "imap": [0, 2, 5, 12], "wait": [0, 12], "idl": [0, 2, 12], "respons": [0, 12], "until": [0, 12], "next": [0, 12], "check": [0, 2, 3, 4, 6, 12], "replac": [0, 3, 4, 8], "alreadysav": 0, "match": [0, 4, 11], "exist": [0, 3, 4, 8], "elasticsearcherror": 0, "elasticsearch": [0, 5, 12], "error": [0, 10, 12], "occur": [0, 7], "create_index": 0, "name": [0, 3, 4, 7, 10, 11, 12], "index": [0, 5, 9, 11, 12], "migrate_index": 0, "aggregate_index": 0, "forensic_index": 0, "updat": [0, 4, 6, 12], "save_aggregate_report_to_elasticsearch": 0, "index_suffix": [0, 12], "index_prefix": [0, 12], "monthly_index": [0, 12], "number_of_shard": [0, 12], "1": [0, 2, 4, 5, 6, 10, 12], "number_of_replica": [0, 12], "suffix": [0, 12], "prefix": [0, 3, 8, 12], "monthli": [0, 12], "instead": [0, 3, 6, 8, 12], "daili": [0, 12], "shard": [0, 12], "replica": [0, 12], "save_forensic_report_to_elasticsearch": 0, "save_smtp_tls_report_to_elasticsearch": 0, "set_host": 0, "use_ssl": 0, "ssl_cert_path": 0, "apikei": [0, 12], "60": [0, 12], "http": [0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12], "chain": 0, "authent": [0, 2, 3, 4, 7, 12], "kei": [0, 3, 4, 6, 12], "opensearcherror": 0, "save_aggregate_report_to_opensearch": 0, "save_forensic_report_to_opensearch": 0, "save_smtp_tls_report_to_opensearch": 0, "class": 0, "hecclient": 0, "access_token": 0, "initi": 0, "hec": [0, 11, 12], "access": [0, 4, 5, 6, 12], "token": [0, 4, 12], "give": [0, 4], "up": [0, 2, 4, 6, 7, 9, 12], "save_aggregate_reports_to_splunk": 0, "dictionari": 0, "save_forensic_reports_to_splunk": 0, "save_smtp_tls_reports_to_splunk": 0, "splunkerror": 0, "might": [0, 3, 7, 8], "other": [0, 3, 4, 7, 8], "project": [0, 2, 3, 5, 11], "downloaderror": 0, "emailparsererror": 0, "convert_outlook_msg": 0, "msg_byte": 0, "msgconvert": [0, 6], "perl": [0, 6], "outlook": [0, 2, 6], "m": [0, 6, 10, 12], "standard": [0, 5, 10], "msg": [0, 6], "decode_base64": 0, "decod": 0, "pad": 0, "being": 0, "get_base_domain": 0, "domain": [0, 4, 7, 8, 10], "get": [0, 2, 4, 6, 12], "base": [0, 2, 3, 4, 7, 8, 10], "ar": [0, 2, 3, 4, 6, 7, 8, 10, 12], "publicsuffix": 0, "org": [0, 6, 9, 10], "public_suffix_list": 0, "dat": 0, "subdomain": [0, 3], "get_filename_safe_str": 0, "safe": 0, "get_ip_address_countri": 0, "ip_address": [0, 10], "db_path": 0, "iso": 0, "code": [0, 4, 5], "countri": [0, 6, 7, 10], "associ": 0, "ipv4": 0, "ipv6": 0, "And": 0, "get_ip_address_info": 0, "cach": [0, 12], "reverse_dns_map": 0, "inform": [0, 4, 6, 7, 12], "expiringdict": 0, "storag": [0, 12], "reverse_dn": [0, 10], "get_reverse_dn": 0, "ani": [0, 3, 7, 8, 12], "get_service_from_reverse_dns_base_domain": 0, "base_domain": [0, 10], "local_file_path": 0, "servic": [0, 3, 4, 5, 7, 8], "lookup": 0, "alwai": [0, 2, 4, 12], "local": [0, 2, 4, 10, 12], "ro": 0, "built": 0, "copi": [0, 6, 11], "If": [0, 3, 4, 6, 7, 8, 12], "unknown": 0, "suppli": [0, 7, 12], "reverse_dns_base_domain": 0, "human_timestamp_to_datetim": 0, "human_timestamp": 0, "to_utc": 0, "human": [0, 7], "readabl": 0, "timestamp": 0, "datetim": 0, "utc": 0, "human_timestamp_to_unix_timestamp": 0, "unix": 0, "yyyi": 0, "mm": 0, "dd": 0, "hh": 0, "ss": 0, "is_mbox": 0, "flag": [0, 2], "is_outlook_msg": 0, "ol": [0, 6], "parse_email": 0, "simplifi": 0, "binari": 0, "query_dn": 0, "record_typ": 0, "about": [0, 5, 6], "record": [0, 5, 6, 10], "answer": [0, 12], "timestamp_to_datetim": 0, "timestamp_to_human": 0, "modul": [0, 5, 12], "pleas": [1, 5, 12], "github": [1, 6, 10, 12], "issu": [1, 5], "tracker": 1, "com": [1, 2, 3, 8, 9, 10, 12], "domainawar": [1, 3, 12], "8": [2, 4, 6, 10, 12], "support": [2, 5, 10, 11], "microsoft": [2, 5, 10, 12], "offic": 2, "365": [2, 4], "via": 2, "graph": [2, 5, 7, 12], "api": [2, 4, 5, 12], "which": [2, 4, 7, 12], "prefer": [2, 6], "over": [2, 5, 7], "organ": [2, 7, 12], "allow": [2, 3, 8, 12], "onli": [2, 3, 6, 7, 8, 12], "exchang": [2, 10, 12], "web": [2, 4], "In": [2, 3, 7, 8, 12], "case": [2, 3, 8], "need": [2, 3, 4, 6, 7, 8, 12], "gatewai": 2, "It": [2, 4, 7, 10, 12], "even": [2, 3, 8, 12], "work": [2, 3, 5, 6, 7, 8], "modern": [2, 3, 8], "auth": [2, 10, 12], "multi": [2, 12], "factor": 2, "To": [2, 4, 6, 7, 9, 10, 12], "thi": [2, 3, 4, 5, 6, 7, 8, 10, 12], "latest": [2, 4, 6, 9], "version": [2, 4, 6, 9, 10, 11, 12], "sourceforg": 2, "net": [2, 10], "unzip": 2, "command": [2, 3, 8, 12], "instal": [2, 5, 12], "java": 2, "sudo": [2, 4, 6, 12], "apt": [2, 4, 6], "jre": 2, "headless": 2, "properti": 2, "see": [2, 3, 4, 5, 7, 12], "document": [2, 12], "basic": [2, 12], "workstat": 2, "mode": [2, 4, 10, 12], "auto": 2, "webdav": 2, "enableew": 2, "office365": 2, "asmx": 2, "listen": [2, 12], "imapport": 2, "1143": 2, "network": [2, 4, 12], "proxi": 2, "enableproxi": 2, "usesystemproxi": 2, "proxyhost": 2, "proxyport": 2, "proxyus": 2, "proxypassword": 2, "exclud": 2, "noproxyfor": 2, "block": [2, 12], "remot": 2, "allowremot": 2, "bind": 2, "socket": 2, "loopback": 2, "bindaddress": 2, "127": [2, 4, 12], "disabl": [2, 12], "specifi": [2, 3], "nosecureimap": 2, "keepal": 2, "charact": [2, 12], "dure": 2, "larg": 2, "enablekeepal": 2, "count": [2, 10], "retriev": 2, "foldersizelimit": 2, "immedi": 2, "store": [2, 4, 9], "imapautoexpung": 2, "enabl": [2, 4, 12], "poll": [2, 12], "delai": [2, 10], "minut": [2, 12], "imapidledelai": 2, "repli": [2, 3, 8], "rfc822": 2, "size": [2, 4], "request": [2, 4, 12], "approxim": 2, "perform": [2, 12], "imapalwaysapproxmsgs": 2, "client": [2, 3, 4, 8, 12], "300": 2, "clientsotimeout": 2, "system": [2, 3, 4, 6, 8, 12], "user": [2, 3, 4, 5, 6, 8, 10, 12], "useradd": [2, 6], "r": [2, 6, 10, 12], "bin": [2, 4, 6, 12], "protect": [2, 3, 5, 8, 12], "pry": [2, 12], "ey": [2, 12], "chown": [2, 12], "root": [2, 12], "opt": [2, 6, 12], "chmod": [2, 4, 12], "u": [2, 6, 10, 12], "rw": [2, 12], "g": [2, 3, 4, 8, 12], "o": [2, 4, 12], "nano": [2, 12], "etc": [2, 3, 4, 6, 8, 12], "unit": [2, 12], "descript": [2, 6, 12], "want": [2, 5, 12], "target": [2, 12], "syslog": [2, 12], "execstart": [2, 12], "group": [2, 7, 12], "restart": [2, 3, 4, 8, 12], "restartsec": [2, 12], "5m": [2, 12], "wantedbi": [2, 12], "Then": [2, 3, 4, 6, 8, 12], "systemctl": [2, 4, 12], "daemon": [2, 4, 12], "reload": [2, 4, 12], "you": [2, 3, 4, 5, 6, 7, 8, 12], "must": [2, 3, 8, 12], "also": [2, 3, 7, 8, 12], "abov": [2, 12], "edit": [2, 6, 12], "everi": [2, 6, 12], "time": [2, 4, 6, 7, 12], "upgrad": [2, 5, 6, 12], "statu": [2, 12], "event": [2, 11, 12], "crash": [2, 4, 12], "5": [2, 4, 9], "show": [2, 7, 12], "log": [2, 12], "current": [2, 4, 12], "vew": 2, "well": [2, 12], "newest": [2, 12], "oldest": [2, 12], "journalctl": [2, 12], "becaus": [2, 3, 7, 8, 12], "interact": [2, 4], "add": [2, 3, 4, 6, 7, 8, 12], "follow": [2, 4], "ini": [2, 12], "config": [2, 6, 12], "demystifi": 3, "complet": [3, 4], "look": [3, 7], "out": [3, 4, 7], "sister": 3, "checkdmarc": 3, "against": [3, 8], "spoof": [3, 8], "open": 3, "monitor": [3, 12], "ensur": [3, 6, 8], "dkm": 3, "mechan": 3, "actual": [3, 10], "same": [3, 4, 6, 7, 11], "end": [3, 4], "pass": [3, 7, 10], "long": 3, "relat": 3, "indic": [3, 5], "signatur": [3, 7, 8], "publish": 3, "envelop": 3, "sign": [3, 4, 6], "vendor": 3, "don": 3, "know": 3, "yet": 3, "ask": 3, "thei": [3, 6, 7, 8, 12], "through": 3, "your": [3, 4, 6, 7, 8, 11, 12], "relai": [3, 8], "theirs": 3, "realli": 3, "why": [3, 7], "displai": [3, 7, 11], "worst": 3, "have": [3, 4, 6, 7, 8, 11, 12], "specif": [3, 12], "norepli": [3, 10], "exampl": [3, 4, 6, 8, 10, 12], "separ": [3, 4, 6, 7, 9, 11, 12], "p": [3, 6, 10], "alter": [3, 8], "sp": [3, 10], "top": [3, 7], "level": [3, 4], "tld": 3, "would": [3, 5, 6, 8], "leav": 3, "vulner": 3, "deploi": [3, 8], "find": [3, 7, 8], "most": [3, 4, 7, 8, 12], "modifi": [3, 8, 12], "footer": [3, 8], "part": [3, 4, 7, 8], "therebi": [3, 8], "break": [3, 4, 8], "ideal": [3, 8], "should": [3, 6, 7, 8, 12], "forward": [3, 7, 8], "without": [3, 4, 7, 8], "all": [3, 5, 7, 8, 11, 12], "joe": [3, 8], "nelson": [3, 8], "doe": [3, 8], "fantast": [3, 8], "job": [3, 6, 8], "explain": [3, 8], "exactli": [3, 8], "shouldn": [3, 8], "fulli": [3, 8], "compliant": [3, 8], "rather": [3, 8], "than": [3, 4, 8, 12], "repeat": [3, 8], "hi": [3, 8], "fine": [3, 8], "here": [3, 8, 10, 12], "summari": [3, 5, 8], "retain": [3, 8], "origin": [3, 8, 12], "2369": [3, 8], "unsubscrib": [3, 8], "outgo": [3, 8, 12], "ad": [3, 6, 8, 12], "link": [3, 4, 7, 8], "2919": [3, 8], "id": [3, 8, 10, 12], "webmail": [3, 7, 8], "gener": [3, 4, 6, 8, 10, 12], "button": [3, 8], "tradit": [3, 8], "disclaim": [3, 8], "addit": [3, 8], "compli": [3, 4, 6, 8, 9], "configur": [3, 4, 5, 6, 7, 8, 9], "action": [3, 8], "still": [3, 6, 8, 10, 12], "tell": [3, 6, 7, 8], "came": [3, 8], "wa": [3, 4, 6, 8], "sent": [3, 8, 12], "post": [3, 8], "step": [3, 4, 8], "common": [3, 4, 6, 8], "platform": [3, 8], "below": [3, 8, 12], "navig": [3, 6, 8], "subject_prefix": [3, 8], "from_is_list": [3, 8], "No": [3, 8], "first_strip_reply_to": [3, 8], "reply_goes_to_list": [3, 8], "poster": [3, 8], "include_rfc2369_head": [3, 8], "ye": [3, 8], "include_list_post_head": [3, 8], "include_sender_head": [3, 8], "non": [3, 8, 12], "digest": [3, 8], "msg_header": [3, 8], "msg_footer": [3, 8], "scrub_nondigest": [3, 8], "privaci": [3, 6, 7, 8, 12], "filter": [3, 7, 8, 11], "dmarc_moderation_act": [3, 8], "accept": [3, 4, 8], "dmarc_quarantine_moderation_act": [3, 8], "dmarc_none_moderation_act": [3, 8], "ident": [3, 8, 12], "blank": [3, 8], "html": [3, 4, 8, 10], "plaintext": [3, 8], "rfc2369": [3, 8], "explicit": [3, 8], "first": [3, 6, 8, 12], "strip": [3, 8, 12], "replyto": [3, 8], "goe": [3, 8], "mung": [3, 8], "mitig": [3, 8], "uncondition": [3, 8], "templat": [3, 8], "unfortun": [3, 8], "postoriu": [3, 8], "admin": [3, 8, 12], "ui": [3, 8], "empti": [3, 8], "so": [3, 6, 7, 8, 12], "ll": [3, 8], "line": [3, 8], "touch": [3, 8], "var": [3, 8], "en": [3, 4, 8, 10], "member": [3, 8], "regular": [3, 8], "languag": [3, 8], "core": [3, 8], "16": [3, 8], "2017a": [3, 8], "higher": [3, 8], "rewrit": [3, 8], "enforc": [3, 8], "quarantin": [3, 8], "reject": [3, 8], "polici": [3, 8, 10, 12], "linux": [3, 6, 8], "go": [3, 8], "legal": [3, 8], "administr": [3, 8], "known": [3, 7, 8, 12], "longer": [3, 8], "wrap": [3, 8], "could": [3, 4, 8, 12], "interfer": [3, 8], "search": [3, 8, 12], "mobil": [3, 8], "On": [3, 4, 6, 7, 8], "hand": [3, 8], "caus": [3, 4, 7, 8], "accident": [3, 8], "entir": [3, 7, 8], "intend": [3, 8], "choos": [3, 8], "fit": [3, 8], "commun": [3, 8], "tab": [3, 4, 8], "page": [3, 4, 6, 7, 8], "visual": [4, 9], "dashboard": [4, 5, 9, 11], "later": [4, 6, 12], "debian": [4, 6], "ubuntu": [4, 6], "y": [4, 6], "transport": [4, 12], "wget": 4, "qo": 4, "artifact": 4, "elast": [4, 5], "co": 4, "gpg": 4, "dearmor": 4, "usr": 4, "share": [4, 12], "keyr": 4, "echo": 4, "deb": 4, "x": [4, 10], "stabl": 4, "main": 4, "tee": 4, "d": 4, "For": [4, 12], "cento": [4, 6], "rhel": [4, 6], "rpm": 4, "guid": [4, 5], "previous": [4, 7], "jvm": 4, "heap": 4, "veri": [4, 7, 12], "small": 4, "1g": 4, "under": [4, 6, 7], "heavi": 4, "load": 4, "fix": 4, "increas": [4, 12], "minimum": 4, "maximum": 4, "depend": [4, 5, 12], "resourc": [4, 5, 12], "sure": [4, 6], "ha": [4, 7, 12], "least": [4, 6, 12], "gb": 4, "ram": 4, "assign": 4, "4": [4, 6, 11], "xms4g": 4, "xmx4g": 4, "www": [4, 6, 12], "refer": [4, 5], "import": [4, 7], "As": [4, 7], "7": [4, 6], "activ": [4, 6], "xpack": 4, "vim": 4, "yml": 4, "featur": 4, "enrol": 4, "encrypt": [4, 12], "logstash": 4, "agent": 4, "keystor": 4, "cert": 4, "p12": 4, "mutual": 4, "between": [4, 7], "cluster": [4, 12], "node": 4, "verification_mod": 4, "truststor": 4, "self": [4, 5], "openssl": 4, "req": 4, "x509": 4, "dai": [4, 9, 12], "newkei": 4, "rsa": 4, "4096": 4, "keyout": 4, "crt": 4, "Or": [4, 6], "csr": 4, "ca": 4, "fill": [4, 6], "prompt": 4, "fqdn": 4, "field": 4, "rm": 4, "f": 4, "place": [4, 7, 12], "mv": 4, "660": 4, "server_ip": 4, "publicbaseurl": 4, "connexion": 4, "9200": [4, 12], "5601": 4, "past": [4, 11], "verif": [4, 12], "put": [4, 12], "browser": 4, "setup": [4, 9, 12], "encryptedsavedobject": 4, "encryptionkei": 4, "xxxx": 4, "now": [4, 7], "parsedmarc": [4, 9, 10, 11], "right": [4, 7], "click": [4, 7], "export": 4, "ndjson": 4, "provid": [4, 7], "consol": [4, 12], "stack": 4, "manag": [4, 12], "hamburg": 4, "menu": [4, 7], "overwrit": 4, "restor": 4, "someon": 4, "els": 4, "permiss": [4, 12], "control": 4, "commerci": [4, 5], "pack": 4, "chang": [4, 7, 11, 12], "wai": [4, 7], "releas": [4, 6], "login": 4, "checkbox": 4, "dmarc_aggreg": 4, "dmarc_forens": 4, "conform": 4, "each": [4, 6, 9, 11], "easi": [4, 9], "regul": [4, 6, 9, 12], "gdpr": [4, 9], "effici": 4, "help": 5, "maintain": 5, "develop": 5, "consid": [5, 7], "review": [5, 7], "how": 5, "contribut": 5, "assist": 5, "pin": 5, "particularli": [5, 12], "thank": [5, 10], "contributor": 5, "cli": 5, "util": 5, "kibana": [5, 11], "splunk": [5, 12], "opensearch": [5, 12], "grafana": 5, "altern": [5, 12], "agari": 5, "brand": [5, 7], "dmarcian": 5, "ondmarc": 5, "proofpoint": 5, "fraud": 5, "defens": 5, "valimail": 5, "draft": [5, 10], "rua": [5, 6], "failur": [5, 7, 10, 12], "ruf": [5, 6, 7, 12], "gmail": [5, 7, 12], "transpar": 5, "handl": [5, 12], "compress": 5, "structur": 5, "simpl": 5, "premad": [5, 11], "apach": 5, "kafka": [5, 12], "prerequisit": 5, "systemd": 5, "pattern": [5, 7], "retent": 5, "owa": 5, "ew": 5, "davmail": 5, "understand": [5, 7], "align": [5, 7, 10], "what": 5, "sender": [5, 7, 8], "won": 5, "t": [5, 8, 12], "dkim": [5, 7, 8, 10], "bug": 5, "tabl": [5, 7], "3": [6, 10, 11, 12], "anoth": [6, 12], "solut": 6, "two": 6, "mailto": 6, "uri": 6, "tag": 6, "comma": [6, 12], "behind": 6, "environ": 6, "detail": [6, 7], "http_proxi": 6, "prox": 6, "3128": 6, "https_proxi": 6, "ftp_proxi": 6, "credenti": [6, 12], "wide": [6, 10], "patch": 6, "2010": [6, 10], "rollup": 6, "22": 6, "kb4295699": 6, "2013": 6, "cumul": 6, "21": 6, "kb4099855": 6, "2016": 6, "11": [6, 10], "kb4134118": 6, "static": 6, "lite": 6, "databas": 6, "ipdb": 6, "distribut": 6, "term": 6, "creativ": 6, "attribut": 6, "intern": 6, "licens": 6, "fallback": 6, "geolite2": 6, "howev": 6, "cannot": 6, "tool": [6, 12], "locat": [6, 7], "overridden": 6, "buster": 6, "compon": 6, "contrib": 6, "repositori": [6, 11], "ppa": 6, "dnf": 6, "build": 6, "maco": 6, "window": 6, "decemb": 6, "30th": 6, "2019": 6, "free": 6, "account": [6, 7], "order": 6, "variou": 6, "regist": 6, "differ": [6, 7, 12], "older": [6, 10], "newer": 6, "Be": 6, "select": 6, "correct": 6, "v": [6, 12], "onc": 6, "pre": 6, "geoip": 6, "conf": 6, "systemdr": 6, "programdata": 6, "citi": 6, "asn": 6, "weekli": 6, "tuesdai": 6, "cron": 6, "schedul": 6, "task": 6, "python3": 6, "pip": 6, "virtualenv": 6, "dev": [6, 12], "libxml2": 6, "libxslt": 6, "python39": 6, "setuptool": 6, "devel": 6, "mkdir": 6, "b": [6, 10], "venv": [6, 12], "those": 6, "explicitli": 6, "9": 6, "insid": 6, "abl": 6, "libemail": 6, "friendli": 7, "incom": [7, 12], "switch": 7, "left": 7, "side": 7, "suggest": 7, "best": 7, "across": 7, "three": 7, "pie": 7, "chart": 7, "percentag": 7, "spf": [7, 10], "segment": 7, "malici": [7, 12], "just": 7, "especi": 7, "collect": [7, 12], "mai": [7, 12], "legitim": [7, 12], "correctli": 7, "while": [7, 12], "remain": 7, "often": 7, "rule": [7, 12], "wherea": 7, "reli": 7, "session": 7, "underneath": 7, "passag": 7, "disposit": [7, 10], "center": 7, "sort": [7, 12], "volum": 7, "By": [7, 12], "hover": 7, "mous": 7, "magnifi": 7, "glass": 7, "icon": 7, "our": 7, "recogn": 7, "market": 7, "plu": 7, "That": 7, "busi": 7, "particular": 7, "With": 7, "contact": 7, "lot": 7, "b2c": 7, "custom": [7, 12], "high": 7, "come": 7, "consum": 7, "googl": [7, 12], "yahoo": 7, "old": 7, "mention": 7, "earlier": 7, "similar": 7, "observ": 7, "who": 7, "addresse": 7, "parent": 7, "subsidiari": 7, "outdat": 7, "further": 7, "down": 7, "were": [7, 12], "call": 7, "been": [7, 12], "consolid": 7, "view": [7, 12], "own": [7, 11], "temporari": 7, "upper": 7, "These": 7, "recipi": 7, "avoid": 7, "leak": 7, "notabl": 7, "chines": 7, "few": [7, 12], "doc": 9, "wiki": 10, "schema": 10, "7480": 10, "appendix": 10, "c": [10, 12], "produc": 10, "normal": [10, 12], "regardless": 10, "xml_schema": 10, "report_metadata": 10, "org_nam": 10, "acm": 10, "org_email": 10, "org_extra_contact_info": 10, "report_id": 10, "9391651994964116463": 10, "begin_d": 10, "2012": 10, "04": 10, "27": 10, "20": 10, "00": 10, "end_dat": 10, "28": 10, "19": 10, "59": 10, "policy_publish": 10, "adkim": 10, "aspf": 10, "pct": 10, "100": [10, 12], "fo": 10, "72": 10, "150": 10, "241": 10, "94": 10, "adsl": 10, "shv": 10, "bellsouth": 10, "policy_evalu": 10, "policy_override_reason": 10, "identifi": 10, "header_from": 10, "envelope_from": 10, "envelope_to": 10, "null": 10, "auth_result": 10, "selector": 10, "scope": [10, 12], "mfrom": 10, "source_ip_address": 10, "source_countri": 10, "source_reverse_dn": 10, "source_base_domain": 10, "spf_align": 10, "dkim_align": 10, "dmarc_align": 10, "policy_override_com": 10, "dkim_domain": 10, "dkim_selector": 10, "dkim_result": 10, "spf_domain": 10, "spf_scope": 10, "spf_result": 10, "xennn": 10, "anonym": 10, "feedback_typ": 10, "user_ag": 10, "lua": 10, "original_mail_from": 10, "sharepoint": 10, "de": 10, "original_rcpt_to": 10, "peter": 10, "pan": 10, "arrival_d": 10, "mon": 10, "01": 10, "oct": 10, "2018": 10, "0200": 10, "message_id": 10, "38": 10, "e7": 10, "30937": 10, "bd6e1bb5": 10, "mailrelai": 10, "authentication_result": 10, "di": 10, "delivery_result": 10, "auth_failur": 10, "reported_domain": 10, "arrival_date_utc": 10, "09": 10, "authentication_mechan": 10, "original_envelope_id": 10, "sample_headers_onli": 10, "servernameon": 10, "n": [10, 12], "tby": 10, "cest": 10, "ndate": 10, "nmessag": 10, "nto": 10, "nfrom": 10, "utf": 10, "sw50zxjha3rpdmugv2v0dgjld2vyymvylcocymvyc2ljahq": 10, "nsubject": 10, "nmime": 10, "nx": 10, "mailer": 10, "foundat": 10, "ncontent": 10, "charset": 10, "transfer": 10, "quot": 10, "printabl": 10, "head": 10, "href": 10, "3d": 10, "nwettbewerb": 10, "doctyp": 10, "w3c": 10, "dtd": 10, "meta": 10, "08": 10, "0240": 10, "003": 10, "parsed_sampl": 10, "display_nam": 10, "interakt": 10, "wettbewerb": 10, "\u00fcbersicht": 10, "to_domain": 10, "timezon": 10, "mime": 10, "hop": 10, "date_utc": 10, "has_defect": 10, "reply_to": 10, "filename_safe_subject": 10, "organization_nam": 10, "inc": 10, "2024": 10, "09t00": 10, "00z": 10, "09t23": 10, "59z": 10, "00z_exampl": 10, "policy_domain": 10, "policy_typ": 10, "st": [10, 12], "policy_str": 10, "stsv1": 10, "mx": 10, "max_ag": 10, "86400": 10, "successful_session_count": 10, "failed_session_count": 10, "failure_detail": 10, "result_typ": 10, "sending_mta_ip": 10, "209": 10, "85": 10, "222": 10, "201": 10, "receiving_ip": 10, "173": 10, "212": 10, "41": 10, "receiving_mx_hostnam": 10, "208": 10, "176": 10, "collector": [11, 12], "editor": 11, "occurr": 11, "layout": 11, "although": 11, "slightli": 11, "easier": 11, "flexibl": 11, "usag": 12, "h": 12, "config_fil": 12, "verbos": 12, "debug": 12, "log_fil": 12, "posit": 12, "argument": 12, "exit": 12, "silent": 12, "impli": 12, "write": 12, "print": 12, "warn": 12, "program": 12, "describ": 12, "comment": 12, "save_aggreg": 12, "save_forens": 12, "dmarcresport": 12, "upersecur": 12, "splunk_hec": 12, "splunkhec": 12, "hectokengoesher": 12, "s3": 12, "bucket": 12, "my": 12, "localhost": 12, "514": 12, "gelf": 12, "logger": 12, "12201": 12, "tcp": 12, "full": 12, "save_smtp_tl": 12, "either": 12, "local_reverse_dns_map_path": 12, "period": 12, "n_proc": 12, "parallel": 12, "larger": 12, "improv": 12, "thousand": 12, "label": 12, "arriv": 12, "993": 12, "escap": 12, "wherev": 12, "section": 12, "recommend": 12, "try": 12, "skip_certificate_verif": 12, "skip": 12, "msgraph": 12, "auth_method": 12, "method": 12, "usernamepassword": 12, "devicecod": 12, "clientsecret": 12, "m365": 12, "client_id": 12, "app": 12, "registr": 12, "client_secret": 12, "secret": 12, "tenant_id": 12, "azur": 12, "tenant": 12, "token_fil": 12, "allow_unencrypted_storag": 12, "fall": 12, "back": 12, "unencrypt": 12, "grant": 12, "readwrit": 12, "deleg": 12, "applic": 12, "restrict": 12, "sinc": 12, "applicationaccesspolici": 12, "powershel": 12, "accessright": 12, "restrictaccess": 12, "appid": 12, "policyscopegroupid": 12, "special": 12, "cert_path": 12, "trust": 12, "appli": 12, "passsword": 12, "aggregate_top": 12, "topic": 12, "forensic_top": 12, "25": 12, "starttl": 12, "upload": 12, "region_nam": 12, "region": 12, "endpoint_url": 12, "endpoint": 12, "access_key_id": 12, "secret_access_kei": 12, "udp": 12, "gmail_api": 12, "credentials_fil": 12, "got": 12, "quickstart": 12, "googleapi": 12, "include_spam_trash": 12, "spam": 12, "trash": 12, "acquir": 12, "oauth2_port": 12, "oauth2": 12, "8080": 12, "paginate_messag": 12, "per": 12, "log_analyt": 12, "resid": 12, "dce": 12, "ingest": 12, "dcr_immutable_id": 12, "immut": 12, "dcr": 12, "dcr_aggregate_stream": 12, "stream": 12, "dcr_forensic_stream": 12, "dcr_smtp_tls_stream": 12, "regard": 12, "strongli": 12, "much": 12, "faster": 12, "reliabl": 12, "cisco": 12, "opendn": 12, "outsid": 12, "instanc": 12, "highli": 12, "industri": 12, "sensit": 12, "healthcar": 12, "financ": 12, "possibl": 12, "appear": 12, "sometim": 12, "kind": 12, "approach": 12, "manual": 12, "1000": 12, "analyz": 12, "year": 12, "_cluster": 12, "health": 12, "pretti": 12, "active_primary_shard": 12, "932": 12, "active_shard": 12, "2k": 12, "persist": 12, "max_shards_per_nod": 12, "2000": 12, "watcher": 12, "io": 12}, "objects": {"": [[0, 0, 0, "-", "parsedmarc"]], "parsedmarc": [[0, 1, 1, "", "InvalidAggregateReport"], [0, 1, 1, "", "InvalidDMARCReport"], [0, 1, 1, "", "InvalidForensicReport"], [0, 1, 1, "", "InvalidSMTPTLSReport"], [0, 1, 1, "", "ParserError"], [0, 0, 0, "-", "elastic"], [0, 2, 1, "", "email_results"], [0, 2, 1, "", "extract_report"], [0, 2, 1, "", "extract_report_from_file_path"], [0, 2, 1, "", "get_dmarc_reports_from_mailbox"], [0, 2, 1, "", "get_dmarc_reports_from_mbox"], [0, 2, 1, "", "get_report_zip"], [0, 0, 0, "-", "opensearch"], [0, 2, 1, "", "parse_aggregate_report_file"], [0, 2, 1, "", "parse_aggregate_report_xml"], [0, 2, 1, "", "parse_forensic_report"], [0, 2, 1, "", "parse_report_email"], [0, 2, 1, "", "parse_report_file"], [0, 2, 1, "", "parse_smtp_tls_report_json"], [0, 2, 1, "", "parsed_aggregate_reports_to_csv"], [0, 2, 1, "", "parsed_aggregate_reports_to_csv_rows"], [0, 2, 1, "", "parsed_forensic_reports_to_csv"], [0, 2, 1, "", "parsed_forensic_reports_to_csv_rows"], [0, 2, 1, "", "parsed_smtp_tls_reports_to_csv"], [0, 2, 1, "", "parsed_smtp_tls_reports_to_csv_rows"], [0, 2, 1, "", "save_output"], [0, 0, 0, "-", "splunk"], [0, 0, 0, "-", "utils"], [0, 2, 1, "", "watch_inbox"]], "parsedmarc.elastic": [[0, 1, 1, "", "AlreadySaved"], [0, 1, 1, "", "ElasticsearchError"], [0, 2, 1, "", "create_indexes"], [0, 2, 1, "", "migrate_indexes"], [0, 2, 1, "", "save_aggregate_report_to_elasticsearch"], [0, 2, 1, "", "save_forensic_report_to_elasticsearch"], [0, 2, 1, "", "save_smtp_tls_report_to_elasticsearch"], [0, 2, 1, "", "set_hosts"]], "parsedmarc.opensearch": [[0, 1, 1, "", "AlreadySaved"], [0, 1, 1, "", "OpenSearchError"], [0, 2, 1, "", "create_indexes"], [0, 2, 1, "", "migrate_indexes"], [0, 2, 1, "", "save_aggregate_report_to_opensearch"], [0, 2, 1, "", "save_forensic_report_to_opensearch"], [0, 2, 1, "", "save_smtp_tls_report_to_opensearch"], [0, 2, 1, "", "set_hosts"]], "parsedmarc.splunk": [[0, 3, 1, "", "HECClient"], [0, 1, 1, "", "SplunkError"]], "parsedmarc.splunk.HECClient": [[0, 4, 1, "", "save_aggregate_reports_to_splunk"], [0, 4, 1, "", "save_forensic_reports_to_splunk"], [0, 4, 1, "", "save_smtp_tls_reports_to_splunk"]], "parsedmarc.utils": [[0, 1, 1, "", "DownloadError"], [0, 1, 1, "", "EmailParserError"], [0, 2, 1, "", "convert_outlook_msg"], [0, 2, 1, "", "decode_base64"], [0, 2, 1, "", "get_base_domain"], [0, 2, 1, "", "get_filename_safe_string"], [0, 2, 1, "", "get_ip_address_country"], [0, 2, 1, "", "get_ip_address_info"], [0, 2, 1, "", "get_reverse_dns"], [0, 2, 1, "", "get_service_from_reverse_dns_base_domain"], [0, 2, 1, "", "human_timestamp_to_datetime"], [0, 2, 1, "", "human_timestamp_to_unix_timestamp"], [0, 2, 1, "", "is_mbox"], [0, 2, 1, "", "is_outlook_msg"], [0, 2, 1, "", "parse_email"], [0, 2, 1, "", "query_dns"], [0, 2, 1, "", "timestamp_to_datetime"], [0, 2, 1, "", "timestamp_to_human"]]}, "objtypes": {"0": "py:module", "1": "py:exception", "2": "py:function", "3": "py:class", "4": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "exception", "Python exception"], "2": ["py", "function", "Python function"], "3": ["py", "class", "Python class"], "4": ["py", "method", "Python method"]}, "titleterms": {"api": 0, "refer": 0, "parsedmarc": [0, 1, 2, 5, 6, 12], "elast": 0, "opensearch": [0, 9], "splunk": [0, 11], "util": 0, "indic": 0, "tabl": 0, "contribut": 1, "bug": 1, "report": [1, 5, 6, 10], "access": 2, "an": 2, "inbox": 2, "us": [2, 6, 7, 12], "owa": 2, "ew": 2, "run": [2, 12], "davmail": 2, "systemd": [2, 12], "servic": [2, 12], "configur": [2, 12], "understand": 3, "dmarc": [3, 5, 7], "resourc": 3, "guid": 3, "spf": 3, "record": [3, 4, 9], "valid": 3, "lookalik": 3, "domain": 3, "align": 3, "what": [3, 8], "sender": 3, "won": 3, "t": 3, "support": 3, "dkim": 3, "about": [3, 8], "mail": [3, 8], "list": [3, 8], "best": [3, 8], "practic": [3, 8], "do": [3, 8], "mailman": [3, 8], "2": [3, 8], "3": [3, 8], "listserv": [3, 8], "workaround": [3, 8], "elasticsearch": 4, "kibana": [4, 7], "instal": [4, 6, 9], "upgrad": 4, "index": 4, "pattern": 4, "retent": [4, 9], "document": 5, "open": 5, "sourc": 5, "analyz": [5, 6], "visual": 5, "featur": 5, "content": 5, "prerequisit": 6, "test": 6, "multipl": 6, "web": 6, "proxi": 6, "microsoft": 6, "exchang": 6, "geoipupd": 6, "setup": 6, "option": 6, "depend": 6, "dashboard": 7, "summari": 7, "forens": [7, 10], "sampl": [7, 10], "grafana": 9, "output": 10, "aggreg": 10, "json": 10, "csv": 10, "smtp": 10, "tl": 10, "cli": 12, "help": 12, "file": 12}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1, "sphinx": 57}, "alltitles": {"API reference": [[0, "api-reference"]], "parsedmarc": [[0, "module-parsedmarc"]], "parsedmarc.elastic": [[0, "module-parsedmarc.elastic"]], "parsedmarc.opensearch": [[0, "module-parsedmarc.opensearch"]], "parsedmarc.splunk": [[0, "module-parsedmarc.splunk"]], "parsedmarc.utils": [[0, "module-parsedmarc.utils"]], "Indices and tables": [[0, "indices-and-tables"]], "Contributing to parsedmarc": [[1, "contributing-to-parsedmarc"]], "Bug reports": [[1, "bug-reports"]], "Accessing an inbox using OWA/EWS": [[2, "accessing-an-inbox-using-owa-ews"]], "Running DavMail as a systemd service": [[2, "running-davmail-as-a-systemd-service"]], "Configuring parsedmarc for DavMail": [[2, "configuring-parsedmarc-for-davmail"]], "Understanding DMARC": [[3, "understanding-dmarc"]], "Resources": [[3, "resources"]], "DMARC guides": [[3, "dmarc-guides"]], "SPF and DMARC record validation": [[3, "spf-and-dmarc-record-validation"]], "Lookalike domains": [[3, "lookalike-domains"]], "DMARC Alignment Guide": [[3, "dmarc-alignment-guide"]], "What if a sender won\u2019t support DKIM/DMARC?": [[3, "what-if-a-sender-wont-support-dkim-dmarc"]], "What about mailing lists?": [[3, "what-about-mailing-lists"], [8, "what-about-mailing-lists"]], "Mailing list best practices": [[3, "mailing-list-best-practices"], [8, "mailing-list-best-practices"]], "Do": [[3, "do"], [8, "do"]], "Do not": [[3, "do-not"], [8, "do-not"]], "Mailman 2": [[3, "mailman-2"], [3, "id1"], [8, "mailman-2"], [8, "id1"]], "Mailman 3": [[3, "mailman-3"], [3, "id2"], [8, "mailman-3"], [8, "id2"]], "LISTSERV": [[3, "listserv"], [8, "listserv"]], "Workarounds": [[3, "workarounds"], [8, "workarounds"]], "Elasticsearch and Kibana": [[4, "elasticsearch-and-kibana"]], "Installation": [[4, "installation"], [6, "installation"], [9, "installation"]], "Upgrading Kibana index patterns": [[4, "upgrading-kibana-index-patterns"]], "Records retention": [[4, "records-retention"], [9, "records-retention"]], "parsedmarc documentation - Open source DMARC report analyzer and visualizer": [[5, "parsedmarc-documentation-open-source-dmarc-report-analyzer-and-visualizer"]], "Features": [[5, "features"]], "Contents": [[5, null]], "Prerequisites": [[6, "prerequisites"]], "Testing multiple report analyzers": [[6, "testing-multiple-report-analyzers"]], "Using a web proxy": [[6, "using-a-web-proxy"]], "Using Microsoft Exchange": [[6, "using-microsoft-exchange"]], "geoipupdate setup": [[6, "geoipupdate-setup"]], "Installing parsedmarc": [[6, "installing-parsedmarc"]], "Optional dependencies": [[6, "optional-dependencies"]], "Using the Kibana dashboards": [[7, "using-the-kibana-dashboards"]], "DMARC Summary": [[7, "dmarc-summary"]], "DMARC Forensic Samples": [[7, "dmarc-forensic-samples"]], "OpenSearch and Grafana": [[9, "opensearch-and-grafana"]], "Sample outputs": [[10, "sample-outputs"]], "Sample aggregate report output": [[10, "sample-aggregate-report-output"]], "JSON aggregate report": [[10, "json-aggregate-report"]], "CSV aggregate report": [[10, "csv-aggregate-report"]], "Sample forensic report output": [[10, "sample-forensic-report-output"]], "JSON forensic report": [[10, "json-forensic-report"]], "CSV forensic report": [[10, "csv-forensic-report"]], "JSON SMTP TLS report": [[10, "json-smtp-tls-report"]], "Splunk": [[11, "splunk"]], "Using parsedmarc": [[12, "using-parsedmarc"]], "CLI help": [[12, "cli-help"]], "Configuration file": [[12, "configuration-file"]], "Running parsedmarc as a systemd service": [[12, "running-parsedmarc-as-a-systemd-service"]]}, "indexentries": {"alreadysaved": [[0, "parsedmarc.elastic.AlreadySaved"], [0, "parsedmarc.opensearch.AlreadySaved"]], "downloaderror": [[0, "parsedmarc.utils.DownloadError"]], "elasticsearcherror": [[0, "parsedmarc.elastic.ElasticsearchError"]], "emailparsererror": [[0, "parsedmarc.utils.EmailParserError"]], "hecclient (class in parsedmarc.splunk)": [[0, "parsedmarc.splunk.HECClient"]], "invalidaggregatereport": [[0, "parsedmarc.InvalidAggregateReport"]], "invaliddmarcreport": [[0, "parsedmarc.InvalidDMARCReport"]], "invalidforensicreport": [[0, "parsedmarc.InvalidForensicReport"]], "invalidsmtptlsreport": [[0, "parsedmarc.InvalidSMTPTLSReport"]], "opensearcherror": [[0, "parsedmarc.opensearch.OpenSearchError"]], "parsererror": [[0, "parsedmarc.ParserError"]], "splunkerror": [[0, "parsedmarc.splunk.SplunkError"]], "convert_outlook_msg() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.convert_outlook_msg"]], "create_indexes() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.create_indexes"]], "create_indexes() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.create_indexes"]], "decode_base64() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.decode_base64"]], "email_results() (in module parsedmarc)": [[0, "parsedmarc.email_results"]], "extract_report() (in module parsedmarc)": [[0, "parsedmarc.extract_report"]], "extract_report_from_file_path() (in module parsedmarc)": [[0, "parsedmarc.extract_report_from_file_path"]], "get_base_domain() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_base_domain"]], "get_dmarc_reports_from_mailbox() (in module parsedmarc)": [[0, "parsedmarc.get_dmarc_reports_from_mailbox"]], "get_dmarc_reports_from_mbox() (in module parsedmarc)": [[0, "parsedmarc.get_dmarc_reports_from_mbox"]], "get_filename_safe_string() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_filename_safe_string"]], "get_ip_address_country() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_ip_address_country"]], "get_ip_address_info() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_ip_address_info"]], "get_report_zip() (in module parsedmarc)": [[0, "parsedmarc.get_report_zip"]], "get_reverse_dns() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_reverse_dns"]], "get_service_from_reverse_dns_base_domain() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_service_from_reverse_dns_base_domain"]], "human_timestamp_to_datetime() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.human_timestamp_to_datetime"]], "human_timestamp_to_unix_timestamp() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.human_timestamp_to_unix_timestamp"]], "is_mbox() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.is_mbox"]], "is_outlook_msg() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.is_outlook_msg"]], "migrate_indexes() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.migrate_indexes"]], "migrate_indexes() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.migrate_indexes"]], "module": [[0, "module-parsedmarc"], [0, "module-parsedmarc.elastic"], [0, "module-parsedmarc.opensearch"], [0, "module-parsedmarc.splunk"], [0, "module-parsedmarc.utils"]], "parse_aggregate_report_file() (in module parsedmarc)": [[0, "parsedmarc.parse_aggregate_report_file"]], "parse_aggregate_report_xml() (in module parsedmarc)": [[0, "parsedmarc.parse_aggregate_report_xml"]], "parse_email() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.parse_email"]], "parse_forensic_report() (in module parsedmarc)": [[0, "parsedmarc.parse_forensic_report"]], "parse_report_email() (in module parsedmarc)": [[0, "parsedmarc.parse_report_email"]], "parse_report_file() (in module parsedmarc)": [[0, "parsedmarc.parse_report_file"]], "parse_smtp_tls_report_json() (in module parsedmarc)": [[0, "parsedmarc.parse_smtp_tls_report_json"]], "parsed_aggregate_reports_to_csv() (in module parsedmarc)": [[0, "parsedmarc.parsed_aggregate_reports_to_csv"]], "parsed_aggregate_reports_to_csv_rows() (in module parsedmarc)": [[0, "parsedmarc.parsed_aggregate_reports_to_csv_rows"]], "parsed_forensic_reports_to_csv() (in module parsedmarc)": [[0, "parsedmarc.parsed_forensic_reports_to_csv"]], "parsed_forensic_reports_to_csv_rows() (in module parsedmarc)": [[0, "parsedmarc.parsed_forensic_reports_to_csv_rows"]], "parsed_smtp_tls_reports_to_csv() (in module parsedmarc)": [[0, "parsedmarc.parsed_smtp_tls_reports_to_csv"]], "parsed_smtp_tls_reports_to_csv_rows() (in module parsedmarc)": [[0, "parsedmarc.parsed_smtp_tls_reports_to_csv_rows"]], "parsedmarc": [[0, "module-parsedmarc"]], "parsedmarc.elastic": [[0, "module-parsedmarc.elastic"]], "parsedmarc.opensearch": [[0, "module-parsedmarc.opensearch"]], "parsedmarc.splunk": [[0, "module-parsedmarc.splunk"]], "parsedmarc.utils": [[0, "module-parsedmarc.utils"]], "query_dns() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.query_dns"]], "save_aggregate_report_to_elasticsearch() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.save_aggregate_report_to_elasticsearch"]], "save_aggregate_report_to_opensearch() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.save_aggregate_report_to_opensearch"]], "save_aggregate_reports_to_splunk() (parsedmarc.splunk.hecclient method)": [[0, "parsedmarc.splunk.HECClient.save_aggregate_reports_to_splunk"]], "save_forensic_report_to_elasticsearch() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.save_forensic_report_to_elasticsearch"]], "save_forensic_report_to_opensearch() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.save_forensic_report_to_opensearch"]], "save_forensic_reports_to_splunk() (parsedmarc.splunk.hecclient method)": [[0, "parsedmarc.splunk.HECClient.save_forensic_reports_to_splunk"]], "save_output() (in module parsedmarc)": [[0, "parsedmarc.save_output"]], "save_smtp_tls_report_to_elasticsearch() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.save_smtp_tls_report_to_elasticsearch"]], "save_smtp_tls_report_to_opensearch() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.save_smtp_tls_report_to_opensearch"]], "save_smtp_tls_reports_to_splunk() (parsedmarc.splunk.hecclient method)": [[0, "parsedmarc.splunk.HECClient.save_smtp_tls_reports_to_splunk"]], "set_hosts() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.set_hosts"]], "set_hosts() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.set_hosts"]], "timestamp_to_datetime() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.timestamp_to_datetime"]], "timestamp_to_human() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.timestamp_to_human"]], "watch_inbox() (in module parsedmarc)": [[0, "parsedmarc.watch_inbox"]]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"API reference": [[0, null]], "Accessing an inbox using OWA/EWS": [[2, null]], "Bug reports": [[1, "bug-reports"]], "CLI help": [[12, "cli-help"]], "CSV aggregate report": [[10, "csv-aggregate-report"]], "CSV forensic report": [[10, "csv-forensic-report"]], "Configuration file": [[12, "configuration-file"]], "Configuring parsedmarc for DavMail": [[2, "configuring-parsedmarc-for-davmail"]], "Contents": [[5, null]], "Contributing to parsedmarc": [[1, null]], "DMARC Alignment Guide": [[3, "dmarc-alignment-guide"]], "DMARC Forensic Samples": [[7, "dmarc-forensic-samples"]], "DMARC Summary": [[7, "dmarc-summary"]], "DMARC guides": [[3, "dmarc-guides"]], "Do": [[3, "do"], [8, "do"]], "Do not": [[3, "do-not"], [8, "do-not"]], "Elasticsearch and Kibana": [[4, null]], "Features": [[5, "features"]], "Indices and tables": [[0, "indices-and-tables"]], "Installation": [[4, "installation"], [6, null], [9, "installation"]], "Installing parsedmarc": [[6, "installing-parsedmarc"]], "JSON SMTP TLS report": [[10, "json-smtp-tls-report"]], "JSON aggregate report": [[10, "json-aggregate-report"]], "JSON forensic report": [[10, "json-forensic-report"]], "LISTSERV": [[3, "listserv"], [8, "listserv"]], "Lookalike domains": [[3, "lookalike-domains"]], "Mailing list best practices": [[3, "mailing-list-best-practices"], [8, "mailing-list-best-practices"]], "Mailman 2": [[3, "mailman-2"], [3, "id1"], [8, "mailman-2"], [8, "id1"]], "Mailman 3": [[3, "mailman-3"], [3, "id2"], [8, "mailman-3"], [8, "id2"]], "OpenSearch and Grafana": [[9, null]], "Optional dependencies": [[6, "optional-dependencies"]], "Prerequisites": [[6, "prerequisites"]], "Records retention": [[4, "records-retention"], [9, "records-retention"]], "Resources": [[3, "resources"]], "Running DavMail as a systemd service": [[2, "running-davmail-as-a-systemd-service"]], "Running parsedmarc as a systemd service": [[12, "running-parsedmarc-as-a-systemd-service"]], "SPF and DMARC record validation": [[3, "spf-and-dmarc-record-validation"]], "Sample aggregate report output": [[10, "sample-aggregate-report-output"]], "Sample forensic report output": [[10, "sample-forensic-report-output"]], "Sample outputs": [[10, null]], "Splunk": [[11, null]], "Testing multiple report analyzers": [[6, "testing-multiple-report-analyzers"]], "Understanding DMARC": [[3, null]], "Upgrading Kibana index patterns": [[4, "upgrading-kibana-index-patterns"]], "Using Microsoft Exchange": [[6, "using-microsoft-exchange"]], "Using a web proxy": [[6, "using-a-web-proxy"]], "Using parsedmarc": [[12, null]], "Using the Kibana dashboards": [[7, null]], "What about mailing lists?": [[3, "what-about-mailing-lists"], [8, null]], "What if a sender won\u2019t support DKIM/DMARC?": [[3, "what-if-a-sender-wont-support-dkim-dmarc"]], "Workarounds": [[3, "workarounds"], [8, "workarounds"]], "geoipupdate setup": [[6, "geoipupdate-setup"]], "parsedmarc": [[0, "module-parsedmarc"]], "parsedmarc documentation - Open source DMARC report analyzer and visualizer": [[5, null]], "parsedmarc.elastic": [[0, "module-parsedmarc.elastic"]], "parsedmarc.opensearch": [[0, "module-parsedmarc.opensearch"]], "parsedmarc.splunk": [[0, "module-parsedmarc.splunk"]], "parsedmarc.utils": [[0, "module-parsedmarc.utils"]]}, "docnames": ["api", "contributing", "davmail", "dmarc", "elasticsearch", "index", "installation", "kibana", "mailing-lists", "opensearch", "output", "splunk", "usage"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1}, "filenames": ["api.md", "contributing.md", "davmail.md", "dmarc.md", "elasticsearch.md", "index.md", "installation.md", "kibana.md", "mailing-lists.md", "opensearch.md", "output.md", "splunk.md", "usage.md"], "indexentries": {"alreadysaved": [[0, "parsedmarc.elastic.AlreadySaved", false], [0, "parsedmarc.opensearch.AlreadySaved", false]], "convert_outlook_msg() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.convert_outlook_msg", false]], "create_indexes() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.create_indexes", false]], "create_indexes() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.create_indexes", false]], "decode_base64() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.decode_base64", false]], "downloaderror": [[0, "parsedmarc.utils.DownloadError", false]], "elasticsearcherror": [[0, "parsedmarc.elastic.ElasticsearchError", false]], "email_results() (in module parsedmarc)": [[0, "parsedmarc.email_results", false]], "emailparsererror": [[0, "parsedmarc.utils.EmailParserError", false]], "extract_report() (in module parsedmarc)": [[0, "parsedmarc.extract_report", false]], "extract_report_from_file_path() (in module parsedmarc)": [[0, "parsedmarc.extract_report_from_file_path", false]], "get_base_domain() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_base_domain", false]], "get_dmarc_reports_from_mailbox() (in module parsedmarc)": [[0, "parsedmarc.get_dmarc_reports_from_mailbox", false]], "get_dmarc_reports_from_mbox() (in module parsedmarc)": [[0, "parsedmarc.get_dmarc_reports_from_mbox", false]], "get_filename_safe_string() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_filename_safe_string", false]], "get_ip_address_country() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_ip_address_country", false]], "get_ip_address_info() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_ip_address_info", false]], "get_report_zip() (in module parsedmarc)": [[0, "parsedmarc.get_report_zip", false]], "get_reverse_dns() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_reverse_dns", false]], "get_service_from_reverse_dns_base_domain() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.get_service_from_reverse_dns_base_domain", false]], "hecclient (class in parsedmarc.splunk)": [[0, "parsedmarc.splunk.HECClient", false]], "human_timestamp_to_datetime() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.human_timestamp_to_datetime", false]], "human_timestamp_to_unix_timestamp() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.human_timestamp_to_unix_timestamp", false]], "invalidaggregatereport": [[0, "parsedmarc.InvalidAggregateReport", false]], "invaliddmarcreport": [[0, "parsedmarc.InvalidDMARCReport", false]], "invalidforensicreport": [[0, "parsedmarc.InvalidForensicReport", false]], "invalidsmtptlsreport": [[0, "parsedmarc.InvalidSMTPTLSReport", false]], "is_mbox() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.is_mbox", false]], "is_outlook_msg() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.is_outlook_msg", false]], "migrate_indexes() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.migrate_indexes", false]], "migrate_indexes() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.migrate_indexes", false]], "module": [[0, "module-parsedmarc", false], [0, "module-parsedmarc.elastic", false], [0, "module-parsedmarc.opensearch", false], [0, "module-parsedmarc.splunk", false], [0, "module-parsedmarc.utils", false]], "opensearcherror": [[0, "parsedmarc.opensearch.OpenSearchError", false]], "parse_aggregate_report_file() (in module parsedmarc)": [[0, "parsedmarc.parse_aggregate_report_file", false]], "parse_aggregate_report_xml() (in module parsedmarc)": [[0, "parsedmarc.parse_aggregate_report_xml", false]], "parse_email() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.parse_email", false]], "parse_forensic_report() (in module parsedmarc)": [[0, "parsedmarc.parse_forensic_report", false]], "parse_report_email() (in module parsedmarc)": [[0, "parsedmarc.parse_report_email", false]], "parse_report_file() (in module parsedmarc)": [[0, "parsedmarc.parse_report_file", false]], "parse_smtp_tls_report_json() (in module parsedmarc)": [[0, "parsedmarc.parse_smtp_tls_report_json", false]], "parsed_aggregate_reports_to_csv() (in module parsedmarc)": [[0, "parsedmarc.parsed_aggregate_reports_to_csv", false]], "parsed_aggregate_reports_to_csv_rows() (in module parsedmarc)": [[0, "parsedmarc.parsed_aggregate_reports_to_csv_rows", false]], "parsed_forensic_reports_to_csv() (in module parsedmarc)": [[0, "parsedmarc.parsed_forensic_reports_to_csv", false]], "parsed_forensic_reports_to_csv_rows() (in module parsedmarc)": [[0, "parsedmarc.parsed_forensic_reports_to_csv_rows", false]], "parsed_smtp_tls_reports_to_csv() (in module parsedmarc)": [[0, "parsedmarc.parsed_smtp_tls_reports_to_csv", false]], "parsed_smtp_tls_reports_to_csv_rows() (in module parsedmarc)": [[0, "parsedmarc.parsed_smtp_tls_reports_to_csv_rows", false]], "parsedmarc": [[0, "module-parsedmarc", false]], "parsedmarc.elastic": [[0, "module-parsedmarc.elastic", false]], "parsedmarc.opensearch": [[0, "module-parsedmarc.opensearch", false]], "parsedmarc.splunk": [[0, "module-parsedmarc.splunk", false]], "parsedmarc.utils": [[0, "module-parsedmarc.utils", false]], "parsererror": [[0, "parsedmarc.ParserError", false]], "query_dns() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.query_dns", false]], "save_aggregate_report_to_elasticsearch() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.save_aggregate_report_to_elasticsearch", false]], "save_aggregate_report_to_opensearch() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.save_aggregate_report_to_opensearch", false]], "save_aggregate_reports_to_splunk() (parsedmarc.splunk.hecclient method)": [[0, "parsedmarc.splunk.HECClient.save_aggregate_reports_to_splunk", false]], "save_forensic_report_to_elasticsearch() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.save_forensic_report_to_elasticsearch", false]], "save_forensic_report_to_opensearch() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.save_forensic_report_to_opensearch", false]], "save_forensic_reports_to_splunk() (parsedmarc.splunk.hecclient method)": [[0, "parsedmarc.splunk.HECClient.save_forensic_reports_to_splunk", false]], "save_output() (in module parsedmarc)": [[0, "parsedmarc.save_output", false]], "save_smtp_tls_report_to_elasticsearch() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.save_smtp_tls_report_to_elasticsearch", false]], "save_smtp_tls_report_to_opensearch() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.save_smtp_tls_report_to_opensearch", false]], "save_smtp_tls_reports_to_splunk() (parsedmarc.splunk.hecclient method)": [[0, "parsedmarc.splunk.HECClient.save_smtp_tls_reports_to_splunk", false]], "set_hosts() (in module parsedmarc.elastic)": [[0, "parsedmarc.elastic.set_hosts", false]], "set_hosts() (in module parsedmarc.opensearch)": [[0, "parsedmarc.opensearch.set_hosts", false]], "splunkerror": [[0, "parsedmarc.splunk.SplunkError", false]], "timestamp_to_datetime() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.timestamp_to_datetime", false]], "timestamp_to_human() (in module parsedmarc.utils)": [[0, "parsedmarc.utils.timestamp_to_human", false]], "watch_inbox() (in module parsedmarc)": [[0, "parsedmarc.watch_inbox", false]]}, "objects": {"": [[0, 0, 0, "-", "parsedmarc"]], "parsedmarc": [[0, 1, 1, "", "InvalidAggregateReport"], [0, 1, 1, "", "InvalidDMARCReport"], [0, 1, 1, "", "InvalidForensicReport"], [0, 1, 1, "", "InvalidSMTPTLSReport"], [0, 1, 1, "", "ParserError"], [0, 0, 0, "-", "elastic"], [0, 2, 1, "", "email_results"], [0, 2, 1, "", "extract_report"], [0, 2, 1, "", "extract_report_from_file_path"], [0, 2, 1, "", "get_dmarc_reports_from_mailbox"], [0, 2, 1, "", "get_dmarc_reports_from_mbox"], [0, 2, 1, "", "get_report_zip"], [0, 0, 0, "-", "opensearch"], [0, 2, 1, "", "parse_aggregate_report_file"], [0, 2, 1, "", "parse_aggregate_report_xml"], [0, 2, 1, "", "parse_forensic_report"], [0, 2, 1, "", "parse_report_email"], [0, 2, 1, "", "parse_report_file"], [0, 2, 1, "", "parse_smtp_tls_report_json"], [0, 2, 1, "", "parsed_aggregate_reports_to_csv"], [0, 2, 1, "", "parsed_aggregate_reports_to_csv_rows"], [0, 2, 1, "", "parsed_forensic_reports_to_csv"], [0, 2, 1, "", "parsed_forensic_reports_to_csv_rows"], [0, 2, 1, "", "parsed_smtp_tls_reports_to_csv"], [0, 2, 1, "", "parsed_smtp_tls_reports_to_csv_rows"], [0, 2, 1, "", "save_output"], [0, 0, 0, "-", "splunk"], [0, 0, 0, "-", "utils"], [0, 2, 1, "", "watch_inbox"]], "parsedmarc.elastic": [[0, 1, 1, "", "AlreadySaved"], [0, 1, 1, "", "ElasticsearchError"], [0, 2, 1, "", "create_indexes"], [0, 2, 1, "", "migrate_indexes"], [0, 2, 1, "", "save_aggregate_report_to_elasticsearch"], [0, 2, 1, "", "save_forensic_report_to_elasticsearch"], [0, 2, 1, "", "save_smtp_tls_report_to_elasticsearch"], [0, 2, 1, "", "set_hosts"]], "parsedmarc.opensearch": [[0, 1, 1, "", "AlreadySaved"], [0, 1, 1, "", "OpenSearchError"], [0, 2, 1, "", "create_indexes"], [0, 2, 1, "", "migrate_indexes"], [0, 2, 1, "", "save_aggregate_report_to_opensearch"], [0, 2, 1, "", "save_forensic_report_to_opensearch"], [0, 2, 1, "", "save_smtp_tls_report_to_opensearch"], [0, 2, 1, "", "set_hosts"]], "parsedmarc.splunk": [[0, 3, 1, "", "HECClient"], [0, 1, 1, "", "SplunkError"]], "parsedmarc.splunk.HECClient": [[0, 4, 1, "", "save_aggregate_reports_to_splunk"], [0, 4, 1, "", "save_forensic_reports_to_splunk"], [0, 4, 1, "", "save_smtp_tls_reports_to_splunk"]], "parsedmarc.utils": [[0, 1, 1, "", "DownloadError"], [0, 1, 1, "", "EmailParserError"], [0, 2, 1, "", "convert_outlook_msg"], [0, 2, 1, "", "decode_base64"], [0, 2, 1, "", "get_base_domain"], [0, 2, 1, "", "get_filename_safe_string"], [0, 2, 1, "", "get_ip_address_country"], [0, 2, 1, "", "get_ip_address_info"], [0, 2, 1, "", "get_reverse_dns"], [0, 2, 1, "", "get_service_from_reverse_dns_base_domain"], [0, 2, 1, "", "human_timestamp_to_datetime"], [0, 2, 1, "", "human_timestamp_to_unix_timestamp"], [0, 2, 1, "", "is_mbox"], [0, 2, 1, "", "is_outlook_msg"], [0, 2, 1, "", "parse_email"], [0, 2, 1, "", "query_dns"], [0, 2, 1, "", "timestamp_to_datetime"], [0, 2, 1, "", "timestamp_to_human"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "exception", "Python exception"], "2": ["py", "function", "Python function"], "3": ["py", "class", "Python class"], "4": ["py", "method", "Python method"]}, "objtypes": {"0": "py:module", "1": "py:exception", "2": "py:function", "3": "py:class", "4": "py:method"}, "terms": {"": [0, 2, 3, 4, 6, 8, 10, 12], "0": [0, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12], "00": 10, "003": 10, "00z": 10, "00z_exampl": 10, "01": 10, "0200": 10, "0240": 10, "04": 10, "08": 10, "09": 10, "09t00": 10, "09t23": 10, "1": [0, 2, 4, 5, 6, 10, 12], "10": [0, 6, 10, 12], "100": [10, 12], "1000": 12, "11": [6, 10], "1143": 2, "12201": 12, "127": [2, 4, 12], "150": 10, "16": [3, 8], "173": 10, "176": 10, "19": 10, "1g": 4, "2": [0, 4, 10, 12], "20": 10, "2000": 12, "201": 10, "2010": [6, 10], "2012": 10, "2013": 6, "2016": 6, "2017a": [3, 8], "2018": 10, "2019": 6, "2024": 10, "208": 10, "209": 10, "21": 6, "212": 10, "22": 6, "222": 10, "2369": [3, 8], "241": 10, "25": 12, "27": 10, "28": 10, "2919": [3, 8], "2k": 12, "3": [6, 10, 11, 12], "30": [0, 12], "300": 2, "30937": 10, "30th": 6, "3128": 6, "365": [2, 4], "38": 10, "3d": 10, "4": [4, 6, 11], "4096": 4, "41": 10, "5": [2, 4, 9], "514": 12, "5601": 4, "59": 10, "59z": 10, "5m": [2, 12], "6": [0, 4, 6, 12], "60": [0, 12], "660": 4, "7": [4, 6], "72": 10, "7480": 10, "8": [2, 4, 6, 10, 12], "8080": 12, "822": 0, "85": 10, "86400": 10, "9": 6, "9200": [4, 12], "932": 12, "9391651994964116463": 10, "94": 10, "993": 12, "A": [0, 3, 12], "And": 0, "As": [4, 7], "Be": 6, "By": [7, 12], "For": [4, 12], "If": [0, 3, 4, 6, 7, 8, 12], "In": [2, 3, 7, 8, 12], "It": [2, 4, 7, 10, 12], "No": [3, 8], "On": [3, 4, 6, 7, 8], "Or": [4, 6], "That": 7, "The": [0, 3, 6, 7, 11, 12], "Then": [2, 3, 4, 6, 8, 12], "These": 7, "To": [2, 4, 6, 7, 9, 10, 12], "With": 7, "_cluster": 12, "_input": 0, "abl": 6, "about": [0, 5, 6], "abov": [2, 12], "accept": [3, 4, 8], "access": [0, 4, 5, 6, 12], "access_key_id": 12, "access_token": 0, "accessright": 12, "accident": [3, 8], "account": [6, 7], "acm": 10, "acquir": 12, "across": 7, "action": [3, 8], "activ": [4, 6], "active_primary_shard": 12, "active_shard": 12, "actual": [3, 10], "ad": [3, 6, 8, 12], "add": [2, 3, 4, 6, 7, 8, 12], "addit": [3, 8], "address": [0, 2, 3, 4, 7, 8, 10, 12], "addresse": 7, "adkim": 10, "admin": [3, 8, 12], "administr": [3, 8], "adsl": 10, "after": [0, 2, 4, 12], "against": [3, 8], "agari": 5, "agent": 4, "aggreg": [0, 5, 7, 11, 12], "aggregate_csv_filenam": [0, 12], "aggregate_index": 0, "aggregate_json_filenam": [0, 12], "aggregate_report": 0, "aggregate_top": 12, "aggregate_url": 12, "align": [5, 7, 10], "aliv": 0, "all": [3, 5, 7, 8, 11, 12], "allow": [2, 3, 8, 12], "allow_unencrypted_storag": 12, "allowremot": 2, "alreadysav": 0, "also": [2, 3, 7, 8, 12], "alter": [3, 8], "altern": [5, 12], "although": 11, "alwai": [0, 2, 4, 12], "always_use_local_fil": [0, 12], "an": [0, 3, 5, 7, 8, 10, 12], "analyz": 12, "ani": [0, 3, 7, 8, 12], "anonym": 10, "anoth": [6, 12], "answer": [0, 12], "apach": 5, "api": [2, 4, 5, 12], "apikei": [0, 12], "app": 12, "appear": 12, "appendix": 10, "appid": 12, "appli": 12, "applic": 12, "applicationaccesspolici": 12, "approach": 12, "approxim": 2, "apt": [2, 4, 6], "ar": [0, 2, 3, 4, 6, 7, 8, 10, 12], "archiv": [0, 12], "archive_fold": [0, 12], "argument": 12, "arriv": 12, "arrival_d": 10, "arrival_date_utc": 10, "artifact": 4, "ask": 3, "asmx": 2, "asn": 6, "aspf": 10, "assign": 4, "assist": 5, "associ": 0, "attach": [0, 3, 8, 10, 12], "attachment_filenam": 0, "attribut": 6, "auth": [2, 10, 12], "auth_failur": 10, "auth_method": 12, "auth_result": 10, "authent": [0, 2, 3, 4, 7, 12], "authentication_mechan": 10, "authentication_result": 10, "auto": 2, "avoid": 7, "azur": 12, "b": [6, 10], "b2c": 7, "back": 12, "base": [0, 2, 3, 4, 7, 8, 10], "base64": 0, "base_domain": [0, 10], "basic": [2, 12], "batch_siz": [0, 12], "bcc": [0, 10], "bd6e1bb5": 10, "becaus": [2, 3, 7, 8, 12], "been": [7, 12], "befor": [0, 12], "begin_d": 10, "behind": 6, "being": 0, "bellsouth": 10, "below": [3, 8, 12], "best": 7, "between": [4, 7], "bin": [2, 4, 6, 12], "binari": 0, "bind": 2, "bindaddress": 2, "blank": [3, 8], "block": [2, 12], "bodi": [0, 3, 8, 10, 12], "bool": [0, 12], "brand": [5, 7], "break": [3, 4, 8], "browser": 4, "bucket": 12, "bug": 5, "build": 6, "built": 0, "busi": 7, "buster": 6, "button": [3, 8], "byte": 0, "c": [10, 12], "ca": 4, "cach": [0, 12], "call": [7, 12], "callabl": 0, "callback": 0, "came": [3, 8], "can": [0, 2, 3, 4, 5, 6, 7, 8, 12], "cannot": 6, "case": [2, 3, 8], "caus": [3, 4, 7, 8], "cc": [0, 10], "center": 7, "cento": [4, 6], "cert": 4, "cert_path": 12, "certif": [0, 4, 12], "cest": 10, "chain": 0, "chang": [4, 7, 11, 12], "charact": [2, 12], "charset": 10, "chart": 7, "check": [0, 2, 3, 4, 6, 12], "check_timeout": [0, 12], "checkbox": 4, "checkdmarc": 3, "chines": 7, "chmod": [2, 4, 12], "choos": [3, 8], "chown": [2, 12], "cisco": 12, "citi": 6, "class": 0, "cli": 5, "click": [4, 7], "client": [2, 3, 4, 8, 12], "client_id": 12, "client_secret": 12, "clientsecret": 12, "clientsotimeout": 2, "cloudflar": [0, 12], "cluster": [4, 12], "co": 4, "code": [0, 4, 5], "collect": [7, 12], "collector": [11, 12], "com": [1, 2, 3, 8, 9, 10, 12], "come": 7, "comma": [6, 12], "command": [2, 3, 8, 12], "comment": 12, "commerci": [4, 5], "common": [3, 4, 6, 8], "commun": [3, 8], "complet": [3, 4], "compli": [3, 4, 6, 8, 9], "compliant": [3, 8], "compon": 6, "compress": 5, "conf": 6, "config": [2, 6, 12], "config_fil": 12, "configur": [3, 4, 5, 6, 7, 8, 9], "conform": 4, "connect": [0, 2, 4, 12], "connexion": 4, "consid": [5, 7], "consist": [0, 5, 10], "consol": [4, 12], "consolid": 7, "consum": 7, "contact": 7, "contain": [0, 7, 11, 12], "content": [0, 3, 8, 10, 11], "contrib": 6, "contribut": 5, "contributor": 5, "control": 4, "convert": [0, 3, 8], "convert_outlook_msg": 0, "copi": [0, 6, 11], "core": [3, 8], "correct": 6, "correctli": 7, "could": [3, 4, 8, 12], "count": [2, 10], "countri": [0, 6, 7, 10], "crash": [2, 4, 12], "creat": [0, 2, 3, 4, 6, 8, 12], "create_fold": 0, "create_index": 0, "creativ": 6, "credenti": [6, 12], "credentials_fil": 12, "cron": 6, "crt": 4, "csr": 4, "csv": [0, 5, 12], "cumul": 6, "current": [2, 4, 12], "custom": [7, 12], "d": 4, "daemon": [2, 4, 12], "dai": [4, 9, 12], "daili": [0, 12], "dashboard": [4, 5, 9, 11], "dat": 0, "data": [0, 4, 5, 7, 9, 11, 12], "databas": 6, "date": [0, 3, 8, 10], "date_utc": 10, "datetim": 0, "davmail": 5, "db_path": 0, "dbip": [0, 12], "dce": 12, "dcr": 12, "dcr_aggregate_stream": 12, "dcr_forensic_stream": 12, "dcr_immutable_id": 12, "dcr_smtp_tls_stream": 12, "dd": 0, "de": 10, "dearmor": 4, "deb": 4, "debian": [4, 6], "debug": 12, "decemb": 6, "decod": 0, "decode_base64": 0, "default": [0, 2, 4, 6, 7, 12], "defens": 5, "delai": [2, 10], "deleg": 12, "delet": [0, 2, 4, 12], "delivery_result": 10, "demystifi": 3, "depend": [4, 5, 12], "deploi": [3, 8], "describ": 12, "descript": [2, 6, 12], "destin": 0, "detail": [6, 7], "dev": [6, 12], "devel": 6, "develop": 5, "devicecod": 12, "di": 10, "dict": 0, "dictionari": 0, "differ": [6, 7, 12], "digest": [3, 8], "directori": [0, 12], "disabl": [2, 12], "disclaim": [3, 8], "displai": [3, 7, 11], "display_nam": 10, "disposit": [7, 10], "distribut": 6, "dkim": [5, 7, 8, 10], "dkim_align": 10, "dkim_domain": 10, "dkim_result": 10, "dkim_selector": 10, "dkm": 3, "dmarc": [0, 4, 6, 8, 9, 10, 11, 12], "dmarc_aggreg": 4, "dmarc_align": 10, "dmarc_forens": 4, "dmarc_moderation_act": [3, 8], "dmarc_none_moderation_act": [3, 8], "dmarc_quarantine_moderation_act": [3, 8], "dmarcian": 5, "dmarcresport": 12, "dn": [0, 3, 7, 12], "dnf": 6, "dns_test_address": 12, "dns_timeout": [0, 12], "do": [0, 2, 6, 7, 12], "doc": 9, "doctyp": 10, "document": [2, 12], "doe": [3, 8], "domain": [0, 4, 7, 8, 10], "domainawar": [1, 3, 12], "don": 3, "down": 7, "download": [0, 2, 4, 6, 12], "downloaderror": 0, "draft": [5, 10], "dtd": 10, "dummi": 12, "dure": 2, "e": [0, 2, 3, 4, 6, 8, 12], "e7": 10, "each": [4, 6, 9, 11], "earlier": 7, "easi": [4, 9], "easier": 11, "echo": 4, "edit": [2, 6, 12], "editor": 11, "effici": 4, "either": 12, "elast": [4, 5], "elasticsearch": [0, 5, 12], "elasticsearcherror": 0, "els": 4, "email": [0, 3, 5, 6, 7, 8, 10, 11, 12], "email_result": 0, "emailparsererror": 0, "empti": [3, 8], "en": [3, 4, 8, 10], "enabl": [2, 4, 12], "enableew": 2, "enablekeepal": 2, "enableproxi": 2, "encod": [0, 10, 12], "encount": 0, "encrypt": [4, 12], "encryptedsavedobject": 4, "encryptionkei": 4, "end": [3, 4], "end_dat": 10, "endpoint": 12, "endpoint_url": 12, "enforc": [3, 8], "enrol": 4, "ensur": [3, 6, 8], "entir": [3, 7, 8], "envelop": 3, "envelope_from": 10, "envelope_to": 10, "environ": 6, "error": [0, 10, 12], "escap": 12, "especi": 7, "etc": [2, 3, 4, 6, 8, 12], "even": [2, 3, 8, 12], "event": [2, 11, 12], "everi": [2, 6, 12], "ew": 5, "exactli": [3, 8], "exampl": [3, 4, 6, 8, 10, 12], "except": [0, 12], "exchang": [2, 10, 12], "exclud": 2, "execstart": [2, 12], "exist": [0, 3, 4, 8], "exit": 12, "expiringdict": 0, "explain": [3, 8], "explicit": [3, 8], "explicitli": 6, "export": 4, "extract": [0, 2], "extract_report": 0, "extract_report_from_file_path": 0, "ey": [2, 12], "f": 4, "factor": 2, "fail": [0, 3, 7, 8, 10, 12], "failed_session_count": 10, "failur": [5, 7, 10, 12], "failure_detail": 10, "fall": 12, "fallback": 6, "fals": [0, 2, 6, 10, 12], "fantast": [3, 8], "faster": 12, "featur": 4, "feedback": 0, "feedback_report": 0, "feedback_typ": 10, "fetch": [0, 12], "few": [7, 12], "field": 4, "file": [0, 2, 5, 6, 11], "file_path": [0, 12], "filenam": [0, 12], "filename_safe_subject": 10, "fill": [4, 6], "filter": [3, 7, 8, 11], "financ": 12, "find": [3, 7, 8], "fine": [3, 8], "first": [3, 6, 8, 12], "first_strip_reply_to": [3, 8], "fit": [3, 8], "fix": 4, "flag": [0, 2], "flat": 0, "flexibl": 11, "flight": 12, "float": [0, 12], "fo": 10, "folder": [0, 2, 12], "foldersizelimit": 2, "follow": [2, 4], "footer": [3, 8], "forens": [0, 5, 11, 12], "forensic_csv_filenam": [0, 12], "forensic_index": 0, "forensic_json_filenam": [0, 12], "forensic_report": 0, "forensic_top": 12, "forensic_url": 12, "format": [0, 6], "forward": [3, 7, 8], "found": [0, 6, 12], "foundat": 10, "fqdn": 4, "fraud": 5, "free": 6, "friendli": 7, "from": [0, 2, 3, 4, 5, 6, 7, 8, 10, 12], "from_is_list": [3, 8], "ftp_proxi": 6, "full": 12, "fulli": [3, 8], "function": 0, "further": 7, "g": [2, 3, 4, 8, 12], "gatewai": 2, "gb": 4, "gdpr": [4, 9], "gelf": 12, "gener": [3, 4, 6, 8, 10, 12], "geoip": 6, "geolite2": 6, "geoloc": [0, 12], "get": [0, 2, 4, 6, 12], "get_base_domain": 0, "get_dmarc_reports_from_mailbox": 0, "get_dmarc_reports_from_mbox": 0, "get_filename_safe_str": 0, "get_ip_address_countri": 0, "get_ip_address_info": 0, "get_report_zip": 0, "get_reverse_dn": 0, "get_service_from_reverse_dns_base_domain": 0, "github": [1, 6, 10, 12], "give": [0, 4], "given": [0, 12], "glass": 7, "gmail": [5, 7, 12], "gmail_api": 12, "go": [3, 8], "goe": [3, 8], "googl": [7, 12], "googleapi": 12, "got": 12, "gpg": 4, "grafana": 5, "grant": 12, "graph": [2, 5, 7, 12], "group": [2, 7, 12], "guid": [4, 5], "gzip": [0, 5], "h": 12, "ha": [4, 7, 12], "hamburg": 4, "hand": [3, 8], "handl": [5, 12], "has_defect": 10, "have": [3, 4, 6, 7, 8, 11, 12], "head": 10, "header": [0, 3, 7, 8, 10, 12], "header_from": 10, "headless": 2, "health": 12, "healthcar": 12, "heap": 4, "heavi": 4, "hec": [0, 11, 12], "hecclient": 0, "hectokengoesher": 12, "help": 5, "here": [3, 8, 10, 12], "hh": 0, "hi": [3, 8], "high": 7, "higher": [3, 8], "highli": 12, "hop": 10, "host": [0, 2, 3, 4, 5, 8, 12], "hostnam": [0, 12], "hover": 7, "how": 5, "howev": 6, "href": 10, "html": [3, 4, 8, 10], "http": [0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12], "http_proxi": 6, "https_proxi": 6, "human": [0, 7], "human_timestamp": 0, "human_timestamp_to_datetim": 0, "human_timestamp_to_unix_timestamp": 0, "i": [0, 2, 3, 4, 5, 6, 7, 8, 10, 12], "icon": 7, "id": [3, 8, 10, 12], "ideal": [3, 8], "ident": [3, 8, 12], "identifi": 10, "idl": [0, 2, 12], "imap": [0, 2, 5, 12], "imapalwaysapproxmsgs": 2, "imapautoexpung": 2, "imapidledelai": 2, "imapport": 2, "immedi": 2, "immut": 12, "impli": 12, "import": [4, 7], "improv": 12, "inbox": [0, 3, 5, 8, 12], "inc": 10, "includ": [0, 3, 6, 7, 8, 12], "include_list_post_head": [3, 8], "include_rfc2369_head": [3, 8], "include_sender_head": [3, 8], "include_spam_trash": 12, "incom": [7, 12], "increas": [4, 12], "index": [0, 5, 9, 11, 12], "index_prefix": [0, 12], "index_suffix": [0, 12], "indic": [3, 5], "individu": 12, "industri": 12, "inform": [0, 4, 6, 7, 12], "ingest": 12, "ini": [2, 12], "initi": 0, "input": 0, "input_": 0, "insid": 6, "instal": [2, 5, 12], "instanc": 12, "instead": [0, 3, 6, 8, 12], "int": [0, 12], "intend": [3, 8], "interact": [2, 4], "interakt": 10, "interfer": [3, 8], "intern": 6, "interv": 12, "invalid": 0, "invalidaggregatereport": 0, "invaliddmarcreport": 0, "invalidforensicreport": 0, "invalidsmtptlsreport": 0, "io": 12, "ip": [0, 3, 4, 6, 7, 12], "ip_address": [0, 10], "ip_db_path": [0, 6, 12], "ipdb": 6, "ipv4": 0, "ipv6": 0, "is_mbox": 0, "is_outlook_msg": 0, "iso": 0, "issu": [1, 5], "java": 2, "job": [3, 6, 8], "joe": [3, 8], "journalctl": [2, 12], "jre": 2, "json": [0, 5, 12], "just": 7, "jvm": 4, "kafka": [5, 12], "kb4099855": 6, "kb4134118": 6, "kb4295699": 6, "keep": 0, "keep_al": 0, "keepal": 2, "kei": [0, 3, 4, 6, 12], "keyout": 4, "keyr": 4, "keystor": 4, "kibana": [5, 11], "kind": 12, "know": 3, "known": [3, 7, 8, 12], "label": 12, "languag": [3, 8], "larg": 2, "larger": 12, "later": [4, 6, 12], "latest": [2, 4, 6, 9], "layer": 0, "layout": 11, "leak": 7, "least": [4, 6, 12], "leav": 3, "left": 7, "legal": [3, 8], "legitim": [7, 12], "level": [3, 4], "libemail": 6, "libxml2": 6, "libxslt": 6, "licens": 6, "like": [0, 3, 6, 8], "limit": [0, 2, 12], "line": [3, 8], "link": [3, 4, 7, 8], "linux": [3, 6, 8], "list": [0, 2, 4, 5, 7, 12], "listen": [2, 12], "lite": 6, "ll": [3, 8], "load": 4, "local": [0, 2, 4, 10, 12], "local_file_path": 0, "local_reverse_dns_map_path": 12, "localhost": 12, "locat": [6, 7, 12], "log": [2, 12], "log_analyt": 12, "log_fil": 12, "logger": 12, "login": 4, "logstash": 4, "long": 3, "longer": [3, 8], "look": [3, 7], "lookup": 0, "loopback": 2, "lot": 7, "lua": 10, "m": [0, 6, 10, 12], "m365": 12, "maco": 6, "magnifi": 7, "mai": [7, 12], "maidir": 12, "mail": [0, 5, 6, 10, 12], "mail_bcc": 0, "mail_cc": 0, "mail_from": 0, "mail_to": 0, "mailbox": [0, 7, 12], "mailbox_connect": 0, "mailboxconnect": 0, "maildir": 12, "maildir_cr": 12, "mailer": 10, "mailrelai": 10, "mailto": 6, "main": 4, "maintain": 5, "make": [0, 3, 4, 8, 9, 12], "malici": [7, 12], "manag": [4, 12], "manual": 12, "map": [0, 12], "market": 7, "match": [0, 4, 11], "max_ag": 10, "max_shards_per_nod": 12, "maximum": 4, "maxmind": [0, 6, 12], "mbox": [0, 12], "mechan": 3, "member": [3, 8], "mention": 7, "menu": [4, 7], "messag": [0, 2, 3, 4, 6, 7, 8, 10, 12], "message_id": 10, "meta": 10, "method": 12, "mfrom": 10, "microsoft": [2, 5, 10, 12], "might": [0, 3, 7, 8], "migrate_index": 0, "mime": 10, "minimum": 4, "minut": [2, 12], "mitig": [3, 8], "mkdir": 6, "mm": 0, "mmdb": [0, 12], "mobil": [3, 8], "mode": [2, 4, 10, 12], "modern": [2, 3, 8], "modifi": [3, 8, 12], "modul": [0, 5, 12], "mon": 10, "monitor": [3, 12], "monthli": [0, 12], "monthly_index": [0, 12], "more": [0, 4, 6, 11, 12], "most": [3, 4, 7, 8, 12], "mous": 7, "move": [0, 4, 12], "msg": [0, 6], "msg_byte": 0, "msg_date": 0, "msg_footer": [3, 8], "msg_header": [3, 8], "msgconvert": [0, 6], "msgraph": 12, "much": 12, "multi": [2, 12], "mung": [3, 8], "must": [2, 3, 8, 12], "mutual": 4, "mv": 4, "mx": 10, "my": 12, "n": [10, 12], "n_proc": 12, "name": [0, 3, 4, 7, 10, 11, 12], "nameserv": [0, 12], "nano": [2, 12], "navig": [3, 6, 8], "ncontent": 10, "ndate": 10, "ndjson": 4, "need": [2, 3, 4, 6, 7, 8, 12], "nelson": [3, 8], "net": [2, 10], "network": [2, 4, 12], "new": [0, 2, 3, 6, 7, 12], "newer": 6, "newest": [2, 12], "newkei": 4, "next": [0, 12], "nfrom": 10, "nmessag": 10, "nmime": 10, "node": 4, "non": [3, 8, 12], "none": [0, 3, 10, 12], "noproxyfor": 2, "norepli": [3, 10], "normal": [10, 12], "nosecureimap": 2, "notabl": 7, "now": [4, 7], "nsubject": 10, "nto": 10, "null": 10, "number": [0, 12], "number_of_replica": [0, 12], "number_of_shard": [0, 12], "nwettbewerb": 10, "nx": 10, "o": [2, 4, 12], "oauth2": 12, "oauth2_port": 12, "object": [0, 4], "observ": 7, "occur": [0, 7], "occurr": 11, "oct": 10, "offic": 2, "office365": 2, "offlin": [0, 12], "often": 7, "ol": [0, 6], "old": 7, "older": [6, 10], "oldest": [2, 12], "onc": 6, "ondmarc": 5, "one": [0, 3, 5, 8, 12], "onli": [2, 3, 6, 7, 8, 12], "onlin": [0, 2, 12], "oor": 0, "open": 3, "opendn": 12, "opensearch": [5, 12], "opensearcherror": 0, "openssl": 4, "opt": [2, 6, 12], "option": [0, 2, 3, 4, 5, 8, 11, 12], "order": 6, "ordereddict": 0, "org": [0, 6, 9, 10], "org_email": 10, "org_extra_contact_info": 10, "org_nam": 10, "organ": [2, 7, 12], "organization_nam": 10, "origin": [3, 8, 12], "original_envelope_id": 10, "original_mail_from": 10, "original_rcpt_to": 10, "other": [0, 3, 4, 7, 8], "our": 7, "out": [3, 4, 7], "outdat": 7, "outgo": [3, 8, 12], "outlook": [0, 2, 6], "output": [0, 5, 12], "output_directori": 0, "outsid": 12, "over": [2, 5, 7], "overrid": [0, 12], "overridden": 6, "overwrit": 4, "owa": 5, "own": [7, 11], "p": [3, 6, 10], "p12": 4, "pack": 4, "packag": [0, 4], "pad": 0, "page": [3, 4, 6, 7, 8], "paginate_messag": 12, "pan": 10, "parallel": 12, "paramet": 0, "parent": 7, "pars": [0, 3, 5, 6, 10, 12], "parse_aggregate_report_fil": 0, "parse_aggregate_report_xml": 0, "parse_email": 0, "parse_forensic_report": 0, "parse_report_email": 0, "parse_report_fil": 0, "parse_smtp_tls_report_json": 0, "parsed_aggregate_reports_to_csv": 0, "parsed_aggregate_reports_to_csv_row": 0, "parsed_forensic_reports_to_csv": 0, "parsed_forensic_reports_to_csv_row": 0, "parsed_sampl": 10, "parsed_smtp_tls_reports_to_csv": 0, "parsed_smtp_tls_reports_to_csv_row": 0, "parsedmarc": [4, 9, 10, 11], "parser": 0, "parsererror": 0, "part": [3, 4, 7, 8], "particular": 7, "particularli": [5, 12], "pass": [3, 7, 10], "passag": 7, "passsword": 12, "password": [0, 4, 6, 12], "past": [4, 11], "patch": 6, "path": [0, 4, 12], "pattern": [5, 7], "payload": [0, 12], "pct": 10, "per": 12, "percentag": 7, "perform": [2, 12], "period": 12, "perl": [0, 6], "permiss": [4, 12], "persist": 12, "peter": 10, "pie": 7, "pin": 5, "pip": 6, "place": [4, 7, 12], "plain": 0, "plaintext": [3, 8], "platform": [3, 8], "pleas": [1, 5, 12], "plu": 7, "polici": [3, 8, 10, 12], "policy_domain": 10, "policy_evalu": 10, "policy_override_com": 10, "policy_override_reason": 10, "policy_publish": 10, "policy_str": 10, "policy_typ": 10, "policyscopegroupid": 12, "poll": [2, 12], "port": [0, 2, 12], "posit": 12, "possibl": 12, "post": [3, 8, 12], "poster": [3, 8], "postoriu": [3, 8], "powershel": 12, "ppa": 6, "pre": [6, 12], "prefer": [2, 6], "prefix": [0, 3, 8, 12], "premad": [5, 11], "prerequisit": 5, "present": 12, "pretti": 12, "previou": [0, 2, 4, 12], "previous": [4, 7], "print": 12, "printabl": 10, "privaci": [3, 6, 7, 8, 12], "process": [0, 2, 5, 6, 12], "produc": 10, "program": 12, "programdata": 6, "project": [0, 2, 3, 5, 11], "prompt": 4, "proofpoint": 5, "properti": 2, "protect": [2, 3, 5, 8, 12], "provid": [4, 7], "prox": 6, "proxi": 2, "proxyhost": 2, "proxypassword": 2, "proxyport": 2, "proxyus": 2, "pry": [2, 12], "public": [0, 3, 10, 12], "public_suffix_list": 0, "publicbaseurl": 4, "publicsuffix": 0, "publish": 3, "put": [4, 12], "python": [0, 5, 6], "python3": 6, "python39": 6, "qo": 4, "quarantin": [3, 8], "queri": [0, 12], "query_dn": 0, "quickstart": 12, "quot": 10, "r": [2, 6, 10, 12], "rais": 0, "ram": 4, "rather": [3, 8], "read": [0, 12], "readabl": 0, "readwrit": 12, "realli": 3, "reason": [0, 2, 4, 12], "receiv": [0, 10, 12], "receiving_ip": 10, "receiving_mx_hostnam": 10, "recipi": 7, "recogn": 7, "recommend": 12, "record": [0, 5, 6, 10], "record_typ": 0, "refer": [4, 5], "regard": 12, "regardless": 10, "region": 12, "region_nam": 12, "regist": 6, "registr": 12, "regul": [4, 6, 9, 12], "regular": [3, 8], "reject": [3, 8], "relai": [3, 8], "relat": 3, "releas": [4, 6], "reli": 7, "reliabl": 12, "reload": [2, 4, 12], "remain": 7, "remot": 2, "remov": [0, 3, 4, 8, 12], "repeat": [3, 8], "replac": [0, 3, 4, 8], "repli": [2, 3, 8], "replica": [0, 12], "reply_goes_to_list": [3, 8], "reply_to": 10, "replyto": [3, 8], "report": [0, 4, 7, 11, 12], "report_id": 10, "report_metadata": 10, "report_typ": 0, "reported_domain": 10, "reports_fold": [0, 12], "repositori": [6, 11], "req": 4, "request": [2, 4, 12], "requir": [0, 2, 3, 4, 6, 8, 12], "require_encrypt": 0, "resid": 12, "resolv": [0, 12], "resourc": [4, 5, 12], "respons": [0, 12], "restart": [2, 3, 4, 8, 12], "restartsec": [2, 12], "restor": 4, "restrict": 12, "restrictaccess": 12, "result": [0, 5, 7, 10, 12], "result_typ": 10, "retain": [3, 8], "retent": 5, "retriev": 2, "return": 0, "revers": [0, 7, 12], "reverse_dn": [0, 10], "reverse_dns_base_domain": 0, "reverse_dns_map": 0, "reverse_dns_map_path": 0, "reverse_dns_map_url": [0, 12], "review": [5, 7], "rewrit": [3, 8], "rfc": [0, 3, 8, 10], "rfc2369": [3, 8], "rfc822": 2, "rhel": [4, 6], "right": [4, 7], "rm": 4, "ro": 0, "rollup": 6, "root": [2, 12], "rpm": 4, "rsa": 4, "rua": [5, 6], "ruf": [5, 6, 7, 12], "rule": [7, 12], "run": [0, 4, 5, 6], "rw": [2, 12], "s3": 12, "safe": 0, "same": [3, 4, 6, 7, 11], "sampl": [0, 5, 12], "sample_headers_onli": 10, "save": [0, 4, 6, 12], "save_aggreg": 12, "save_aggregate_report_to_elasticsearch": 0, "save_aggregate_report_to_opensearch": 0, "save_aggregate_reports_to_splunk": 0, "save_forens": 12, "save_forensic_report_to_elasticsearch": 0, "save_forensic_report_to_opensearch": 0, "save_forensic_reports_to_splunk": 0, "save_output": 0, "save_smtp_tl": 12, "save_smtp_tls_report_to_elasticsearch": 0, "save_smtp_tls_report_to_opensearch": 0, "save_smtp_tls_reports_to_splunk": 0, "schedul": 6, "schema": 10, "scope": [10, 12], "scrub_nondigest": [3, 8], "search": [3, 8, 12], "second": [0, 2, 12], "secret": 12, "secret_access_kei": 12, "section": 12, "secur": [0, 4, 12], "see": [2, 3, 4, 5, 7, 12], "segment": 7, "select": 6, "selector": 10, "self": [4, 5], "send": [0, 2, 3, 4, 5, 7, 8, 11, 12], "sender": [5, 7, 8], "sending_mta_ip": 10, "sensit": 12, "sent": [3, 8, 12], "separ": [3, 4, 6, 7, 9, 11, 12], "server": [0, 2, 3, 4, 6, 7, 10, 12], "server_ip": 4, "servernameon": 10, "servic": [0, 3, 4, 5, 7, 8], "session": 7, "set": [0, 2, 3, 4, 6, 7, 8, 9, 12], "set_host": 0, "setup": [4, 9, 12], "setuptool": 6, "shard": [0, 12], "share": [4, 12], "sharepoint": 10, "should": [3, 6, 7, 8, 12], "shouldn": [3, 8], "show": [2, 7, 12], "shv": 10, "side": 7, "sign": [3, 4, 6], "signatur": [3, 7, 8], "silent": 12, "similar": 7, "simpl": 5, "simplifi": 0, "sinc": 12, "singl": 0, "sister": 3, "size": [2, 4], "skip": 12, "skip_certificate_verif": 12, "slightli": 11, "small": 4, "smtp": [0, 3, 7, 12], "smtp_tl": [0, 12], "smtp_tls_csv_filenam": 0, "smtp_tls_json_filenam": 0, "smtp_tls_url": 12, "so": [3, 6, 7, 8, 12], "socket": 2, "solut": 6, "some": [0, 2, 3, 4, 7, 8], "someon": 4, "sometim": 12, "sort": [7, 12], "sourc": [0, 3, 4, 6, 7, 10], "source_base_domain": 10, "source_countri": 10, "source_ip_address": 10, "source_reverse_dn": 10, "sourceforg": 2, "sp": [3, 10], "spam": 12, "special": 12, "specif": [3, 12], "specifi": [2, 3], "spf": [7, 10], "spf_align": 10, "spf_domain": 10, "spf_result": 10, "spf_scope": 10, "splunk": [5, 12], "splunk_hec": 12, "splunkerror": 0, "splunkhec": 12, "spoof": [3, 8], "ss": 0, "ssl": [0, 2, 4, 12], "ssl_cert_path": 0, "st": [10, 12], "stabl": 4, "stack": 4, "standard": [0, 5, 10], "start": [0, 2, 4, 6, 7, 9, 11, 12], "starttl": 12, "static": 6, "statu": [2, 12], "step": [3, 4, 8], "still": [3, 6, 8, 10, 12], "storag": [0, 12], "store": [2, 4, 9], "str": [0, 12], "stream": 12, "string": 0, "strip": [3, 8, 12], "strip_attachment_payload": [0, 12], "strongli": 12, "structur": 5, "stsv1": 10, "subdomain": [0, 3], "subject": [0, 3, 8, 10, 12], "subject_prefix": [3, 8], "subsidiari": 7, "successful_session_count": 10, "sudo": [2, 4, 6, 12], "suffix": [0, 12], "suggest": 7, "suitabl": 0, "summari": [3, 5, 8], "suppli": [0, 7, 12], "support": [2, 5, 10, 11], "sure": [4, 6], "sw50zxjha3rpdmugv2v0dgjld2vyymvylcocymvyc2ljahq": 10, "switch": 7, "syslog": [2, 12], "system": [2, 3, 4, 6, 8, 12], "systemctl": [2, 4, 12], "systemd": 5, "systemdr": 6, "t": [5, 8, 12], "tab": [3, 4, 8], "tabl": [5, 7], "tag": 6, "target": [2, 12], "task": 6, "tby": 10, "tcp": 12, "tee": 4, "tell": [3, 6, 7, 8], "templat": [3, 8], "temporari": 7, "tenant": 12, "tenant_id": 12, "term": 6, "test": [0, 10, 12], "text": [0, 10], "than": [3, 4, 8, 12], "thank": [5, 10], "thei": [3, 6, 7, 8, 12], "theirs": 3, "them": [0, 4, 7, 12], "therebi": [3, 8], "thi": [2, 3, 4, 5, 6, 7, 8, 10, 12], "those": 6, "thousand": 12, "three": 7, "through": 3, "time": [2, 4, 6, 7, 12], "timeout": [0, 2, 12], "timestamp": 0, "timestamp_to_datetim": 0, "timestamp_to_human": 0, "timezon": 10, "tl": [0, 12], "tld": 3, "to_domain": 10, "to_utc": 0, "token": [0, 4, 12], "token_fil": 12, "tool": [6, 12], "top": [3, 7], "topic": 12, "touch": [3, 8], "tracker": 1, "tradit": [3, 8], "transfer": 10, "transpar": 5, "transport": [4, 12], "trash": 12, "true": [0, 2, 4, 10, 12], "trust": 12, "truststor": 4, "try": 12, "tuesdai": 6, "two": 6, "type": [0, 10, 12], "u": [2, 6, 10, 12], "ubuntu": [4, 6], "udp": 12, "ui": [3, 8], "uncondition": [3, 8], "under": [4, 6, 7], "underneath": 7, "understand": [5, 7], "unencrypt": 12, "unfortun": [3, 8], "unit": [2, 12], "unix": 0, "unknown": 0, "unsubscrib": [3, 8], "until": [0, 12], "unzip": 2, "up": [0, 2, 4, 6, 7, 9, 12], "updat": [0, 4, 6, 12], "upersecur": 12, "upgrad": [2, 5, 6, 12], "upload": 12, "upper": 7, "uri": 6, "url": [0, 2, 12], "us": [0, 3, 4, 5, 8, 10], "usag": 12, "use_ssl": 0, "user": [2, 3, 4, 5, 6, 8, 10, 12], "user_ag": 10, "useradd": [2, 6], "usernam": [0, 12], "usernamepassword": 12, "usesystemproxi": 2, "usr": 4, "utc": 0, "utf": 10, "util": 5, "v": [6, 12], "valid": [0, 7, 10, 12], "valimail": 5, "valu": [0, 3, 4, 7, 8, 12], "var": [3, 8], "variou": 6, "vendor": 3, "venv": [6, 12], "verbos": 12, "veri": [4, 7, 12], "verif": [4, 12], "verifi": 0, "verification_mod": 4, "version": [2, 4, 6, 9, 10, 11, 12], "vew": 2, "via": 2, "view": [7, 12], "vim": 4, "virtualenv": 6, "visual": [4, 9], "volum": 7, "vulner": 3, "w3c": 10, "wa": [3, 4, 6, 8], "wai": [4, 7], "wait": [0, 12], "want": [2, 5, 12], "wantedbi": [2, 12], "warn": 12, "watch": [0, 2, 4, 12], "watch_inbox": 0, "watcher": 12, "web": [2, 4], "webdav": 2, "webhook": 12, "webmail": [3, 7, 8], "weekli": 6, "well": [2, 12], "were": [7, 12], "wettbewerb": 10, "wget": 4, "what": 5, "when": [0, 3, 5, 7, 8, 12], "whenev": [0, 2, 12], "where": [0, 2, 3, 8, 12], "wherea": 7, "wherev": 12, "whether": 0, "which": [2, 4, 7, 12], "while": [7, 12], "who": 7, "why": [3, 7], "wide": [6, 10], "wiki": 10, "window": 6, "without": [3, 4, 7, 8], "won": 5, "work": [2, 3, 5, 6, 7, 8], "workstat": 2, "worst": 3, "would": [3, 5, 6, 8], "wrap": [3, 8], "write": 12, "www": [4, 6, 12], "x": [4, 10], "x509": 4, "xennn": 10, "xml": [0, 11], "xml_schema": 10, "xms4g": 4, "xmx4g": 4, "xpack": 4, "xxxx": 4, "y": [4, 6], "yahoo": 7, "ye": [3, 8], "year": 12, "yet": 3, "yml": 4, "you": [2, 3, 4, 5, 6, 7, 8, 12], "your": [3, 4, 6, 7, 8, 11, 12], "yyyi": 0, "zip": [0, 2, 5, 12], "\u00fcbersicht": 10}, "titles": ["API reference", "Contributing to parsedmarc", "Accessing an inbox using OWA/EWS", "Understanding DMARC", "Elasticsearch and Kibana", "parsedmarc documentation - Open source DMARC report analyzer and visualizer", "Installation", "Using the Kibana dashboards", "What about mailing lists?", "OpenSearch and Grafana", "Sample outputs", "Splunk", "Using parsedmarc"], "titleterms": {"2": [3, 8], "3": [3, 8], "about": [3, 8], "access": 2, "aggreg": 10, "align": 3, "an": 2, "analyz": [5, 6], "api": 0, "best": [3, 8], "bug": 1, "cli": 12, "configur": [2, 12], "content": 5, "contribut": 1, "csv": 10, "dashboard": 7, "davmail": 2, "depend": 6, "dkim": 3, "dmarc": [3, 5, 7], "do": [3, 8], "document": 5, "domain": 3, "elast": 0, "elasticsearch": 4, "ew": 2, "exchang": 6, "featur": 5, "file": 12, "forens": [7, 10], "geoipupd": 6, "grafana": 9, "guid": 3, "help": 12, "inbox": 2, "index": 4, "indic": 0, "instal": [4, 6, 9], "json": 10, "kibana": [4, 7], "list": [3, 8], "listserv": [3, 8], "lookalik": 3, "mail": [3, 8], "mailman": [3, 8], "microsoft": 6, "multipl": 6, "open": 5, "opensearch": [0, 9], "option": 6, "output": 10, "owa": 2, "parsedmarc": [0, 1, 2, 5, 6, 12], "pattern": 4, "practic": [3, 8], "prerequisit": 6, "proxi": 6, "record": [3, 4, 9], "refer": 0, "report": [1, 5, 6, 10], "resourc": 3, "retent": [4, 9], "run": [2, 12], "sampl": [7, 10], "sender": 3, "servic": [2, 12], "setup": 6, "smtp": 10, "sourc": 5, "spf": 3, "splunk": [0, 11], "summari": 7, "support": 3, "systemd": [2, 12], "t": 3, "tabl": 0, "test": 6, "tl": 10, "understand": 3, "upgrad": 4, "us": [2, 6, 7, 12], "util": 0, "valid": 3, "visual": 5, "web": 6, "what": [3, 8], "won": 3, "workaround": [3, 8]}}) \ No newline at end of file diff --git a/splunk.html b/splunk.html index 87a190c6..021cd233 100644 --- a/splunk.html +++ b/splunk.html @@ -1,24 +1,21 @@ + + - + - + - Splunk — parsedmarc 8.15.0 documentation - - + Splunk — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -
    - 8.15.0 -
    @@ -88,7 +82,7 @@
    -

    Splunk

    +

    Splunk

    Starting in version 4.3.0 parsedmarc supports sending aggregate and/or forensic DMARC data to a Splunk HTTP Event collector (HEC).

    The project repository contains XML files for premade Splunk diff --git a/usage.html b/usage.html index d9837c41..801dc99d 100644 --- a/usage.html +++ b/usage.html @@ -1,24 +1,21 @@ + + - + - + - Using parsedmarc — parsedmarc 8.15.0 documentation - - + Using parsedmarc — parsedmarc 8.15.1 documentation + + - - - - - - - - + + + + + @@ -37,9 +34,6 @@ parsedmarc -

    - 8.15.0 -
    @@ -93,9 +87,9 @@
    -

    Using parsedmarc

    +

    Using parsedmarc

    -

    CLI help

    +

    CLI help

    usage: parsedmarc [-h] [-c CONFIG_FILE] [--strip-attachment-payloads] [-o OUTPUT]
                        [--aggregate-json-filename AGGREGATE_JSON_FILENAME]
                        [--forensic-json-filename FORENSIC_JSON_FILENAME]
    @@ -147,7 +141,7 @@ 

    CLI help -

    Configuration file

    +

    Configuration file

    parsedmarc can be configured by supplying the path to an INI file

    The full set of configuration options are:

    @@ -221,6 +221,8 @@

    Configuration filereverse_dns_map_url - Overrides the default download URL for the reverse DNS map

  • nameservers - str: A comma separated list of DNS resolvers (Default: [Cloudflare's public resolvers])

  • +
  • dns_test_address - str: a dummy address used for DNS pre-flight checks +(Default: 1.1.1.1)

  • dns_timeout - float: DNS timeout period

  • debug - bool: Print debugging messages

  • silent - bool: Only print errors (Default: True)

  • @@ -489,6 +491,20 @@

    Configuration filemode - str: The GELF transport type to use. Valid modes: tcp, udp, tls

    +
  • maildir

    +
      +
    • reports_folder - str: Full path for mailbox maidir location (Default: INBOX)

    • +
    • maildir_create - bool: Create maildir if not present (Default: False)

    • +
    +
  • +
  • webhook - Post the individual reports to a webhook url with the report as the JSON body

    +
      +
    • aggregate_url - str: URL of the webhook which should receive the aggregate reports

    • +
    • forensic_url - str: URL of the webhook which should receive the forensic reports

    • +
    • smtp_tls_url - str: URL of the webhook which should receive the smtp_tls reports

    • +
    • timeout - int: Interval in which the webhook call should timeout

    • +
    +
  • Warning

    @@ -551,7 +567,7 @@

    Configuration file -

    Running parsedmarc as a systemd service

    +

    Running parsedmarc as a systemd service

    Use systemd to run parsedmarc as a service and process reports as they arrive.

    Protect the parsedmarc configuration file from prying eyes

    Setting

    Value