Skip to content

syshttp.autopatch

syshttp.autopatch

Patch HTTP client libraries as they are imported, so they route and verify the way syshttp does: routing by :func:syshttp.proxy.proxy_map through proxylib's client integrations (proxylib.patch), and TLS by the system's context (:func:syshttp.tls.os_ssl_context).

SYSHTTP_IMPORTPATCH chooses the libraries -- read under the prefixes as every setting is (SYSHTTP_PREFIX=AGENTS_: AGENTS_SYSHTTP_IMPORTPATCH): 1/true/yes/on for all of :data:TARGETS, 0/false/no/off, empty or unset for none, else their names separated by : (requests:httpx; a module name such as urllib.request works too). It is read when the library is imported: importpatch (a dependency) calls :func:importpatch for each target, through this distribution's importpatch entry points. :func:patch applies the same patches from code, whatever the setting says.

What the caller chose always wins: a proxy given to the client, a context, a CA bundle or verify=False.

enabled(*, env=None)

The names of :data:TARGETS that :func:setting_name enables, : separated. A name that is not a target is logged at WARNING and skipped.

Source code in src/syshttp/autopatch.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def enabled(*, env: Optional[HTTPEnv] = None) -> FrozenSet[str]:
    """The names of :data:`TARGETS` that :func:`setting_name` enables, ``:``
    separated. A name that is not a target is logged at WARNING and skipped."""
    env = HTTPEnv() if env is None else env
    name = setting_name(env=env)
    raw = env.get(SETTING) or ""
    if raw.lower() in _FALSE:
        return frozenset()
    if raw.lower() in _TRUE:
        return frozenset(TARGETS)
    names = set()
    for part in raw.split(":"):
        part = part.strip()
        if not part:
            continue
        target = _BY_MODULE.get(part, part)
        if target in TARGETS:
            names.add(target)
        else:
            _log.warning("%s names %r, which is not one of %s", name, part, ", ".join(TARGETS))
    return frozenset(names)

importpatch(name, module)

importpatch's entry point: patch module, just imported as name, when :func:setting_name enables it.

Source code in src/syshttp/autopatch.py
89
90
91
92
93
94
95
def importpatch(name: str, module: ModuleType) -> None:
    """importpatch's entry point: patch ``module``, just imported as ``name``,
    when :func:`setting_name` enables it."""
    _flush()
    target = _BY_MODULE.get(name)
    if target is not None and target in enabled():
        _apply(target, module)

patch(targets=None)

Patch targets now (default: every target whose library is installed), importing each library. A name that is not a target is a ValueError; a named library that is not installed raises its ImportError. Returns the names patched; patching twice does nothing.

Source code in src/syshttp/autopatch.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def patch(targets: Optional[Iterable[str]] = None) -> Tuple[str, ...]:
    """Patch ``targets`` now (default: every target whose library is
    installed), importing each library. A name that is not a target is a
    ``ValueError``; a named library that is not installed raises its
    ``ImportError``. Returns the names patched; patching twice does nothing."""
    names = list(TARGETS) if targets is None else [_BY_MODULE.get(t, t) for t in targets]
    for name in names:
        if name not in TARGETS:
            raise ValueError("%r is not one of %s" % (name, ", ".join(TARGETS)))
    _flush()
    done = []
    for name in names:
        try:
            module = importlib.import_module(TARGETS[name])
        except ImportError:
            if targets is not None:
                raise
            continue
        _apply(name, module)
        done.append(name)
    return tuple(done)

patched()

The names patched now, in the order applied.

Source code in src/syshttp/autopatch.py
131
132
133
134
def patched() -> Tuple[str, ...]:
    """The names patched now, in the order applied."""
    with _lock:
        return tuple(_undo)

setting_name(*, env=None)

The variable that chooses the libraries: <PREFIX>SYSHTTP_IMPORTPATCH for the first prefix that sets it (AGENTS_SYSHTTP_IMPORTPATCH under SYSHTTP_PREFIX=AGENTS_), else the name under the first prefix.

Source code in src/syshttp/autopatch.py
57
58
59
60
61
62
63
def setting_name(*, env: Optional[HTTPEnv] = None) -> str:
    """The variable that chooses the libraries: ``<PREFIX>SYSHTTP_IMPORTPATCH``
    for the first prefix that sets it (``AGENTS_SYSHTTP_IMPORTPATCH`` under
    ``SYSHTTP_PREFIX=AGENTS_``), else the name under the first prefix."""
    env = HTTPEnv() if env is None else env
    found = env.find(SETTING)
    return found[0] if found else env.name(SETTING)

unpatch()

Undo every patch, the last applied first. A client built while patched keeps what it was given; proxylib's integrations stay installed, back on proxylib's default map.

Source code in src/syshttp/autopatch.py
121
122
123
124
125
126
127
128
def unpatch() -> None:
    """Undo every patch, the last applied first. A client built while patched
    keeps what it was given; proxylib's integrations stay installed, back on
    proxylib's default map."""
    with _lock:
        for name in reversed(list(_undo)):
            for step in reversed(_undo.pop(name)):
                step()