"""Re-send only when the TLS handshake died or the connection never opened, so no HTTP bytes can have left this attempt.

other defaults to 0 here, so an error that may have followed the request stops the retry loop even if you leave it out:

    retries = HandshakeEOFRetry(total=6, other=0, backoff_factor=1)
    session.mount("https://", HTTPAdapter(max_retries=retries))

It tests where the error was raised, not what its text says, because the text differs between TLS libraries.
_is_connection_error is a private urllib3 method: recheck this file when you upgrade.
"""
import ssl

from urllib3.util.retry import Retry


# What a handshake looks like when the connection ends under it, across the TLS builds tested.
CONNECTION_ENDED = (ssl.SSLEOFError, ssl.SSLZeroReturnError, ssl.SSLSyscallError, ConnectionError)


def frames_of(error):
    frames, step = [], error.__traceback__
    while step is not None:
        frames.append(step.tb_frame)
        step = step.tb_next
    return frames

def writing_the_request(frame):
    return frame.f_code.co_name in ("request", "request_chunked") and frame.f_globals.get("__name__") == "urllib3.connection"

def died_in_handshake(error):
    """True if error wraps a closed or reset connection raised inside do_handshake(), for this attempt's own connection.

    It walks only what this attempt wrapped. __context__ stays out: a call made inside an except block inherits the
    earlier failure there, and walking it would re-send a POST that reached the target. An error that came up through
    urllib3's request() doesn't count: the request was being written, and a body generator that opens HTTPS can fail
    in its own handshake there. Called on an error from your own code, this tests the last attempt only: with redirects
    or a mounted Retry on, an earlier attempt may have reached the target.
    """
    seen = set()
    while isinstance(error, BaseException) and id(error) not in seen:
        seen.add(id(error))
        frames = frames_of(error)
        if any(writing_the_request(frame) for frame in frames):
            return False
        if isinstance(error, CONNECTION_ENDED):
            return bool(frames) and frames[-1].f_code.co_name == "do_handshake"
        wrapped = [getattr(error, "original_error", None), getattr(error, "reason", None), *error.args, error.__cause__]
        error = next((item for item in wrapped if isinstance(item, BaseException)), None)
    return False


class HandshakeEOFRetry(Retry):
    """Counts a handshake that died as a connection error, retried for every method. other defaults to 0. Nothing else changes."""

    def __init__(self, *args, other=0, **kwargs):
        super().__init__(*args, other=other, **kwargs)

    def _is_connection_error(self, err):
        return super()._is_connection_error(err) or died_in_handshake(err)
