Try to convert an ``ssl_options`` dictionary to an
    `~ssl.SSLContext` object.

    The ``ssl_options`` dictionary contains keywords to be passed to
    `ssl.wrap_socket`.  In Python 2.7.9+, `ssl.SSLContext` objects can
    be used instead.  This function converts the dict form to its
    `~ssl.SSLContext` equivalent, and may be used when a component which
    accepts both forms needs to upgrade to the `~ssl.SSLContext` version
    to use features like SNI or NPN.

def ssl_options_to_context(
    ssl_options: Union[Dict[str, Any], ssl.SSLContext]
) -> ssl.SSLContext:
    """Try to convert an ``ssl_options`` dictionary to an
    `~ssl.SSLContext` object.

    The ``ssl_options`` dictionary contains keywords to be passed to
    `ssl.wrap_socket`.  In Python 2.7.9+, `ssl.SSLContext` objects can
    be used instead.  This function converts the dict form to its
    `~ssl.SSLContext` equivalent, and may be used when a component which
    accepts both forms needs to upgrade to the `~ssl.SSLContext` version
    to use features like SNI or NPN.
    """
    if isinstance(ssl_options, ssl.SSLContext):
        return ssl_options
    assert isinstance(ssl_options, dict)
    assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options
    # Can't use create_default_context since this interface doesn't
    # tell us client vs server.
    context = ssl.SSLContext(ssl_options.get("ssl_version", ssl.PROTOCOL_SSLv23))
    if "certfile" in ssl_options:
        context.load_cert_chain(
            ssl_options["certfile"], ssl_options.get("keyfile", None)
        )
    if "cert_reqs" in ssl_options:
        context.verify_mode = ssl_options["cert_reqs"]
    if "ca_certs" in ssl_options:
        context.load_verify_locations(ssl_options["ca_certs"])
    if "ciphers" in ssl_options:
        context.set_ciphers(ssl_options["ciphers"])
    if hasattr(ssl, "OP_NO_COMPRESSION"):
        # Disable TLS compression to avoid CRIME and related attacks.
        # This constant depends on openssl version 1.0.
        # TODO: Do we need to do this ourselves or can we trust
        # the defaults?
        context.options |= ssl.OP_NO_COMPRESSION
    return context