Skip to content

PAC support

proxylib.pac

PAC (Proxy Auto-Config) support.

  • :data:functions -- the PAC builtins (Netscape set plus Microsoft *Ex).
  • :class:PacScript -- a script running in a JavaScript engine (proxylib[pac] for quickjs, proxylib[dukpy] for dukpy), with a time limit per evaluation.
  • :class:PacProxyMap -- the proxy map a script (or a Python function) answers.
  • :func:load / :func:loads -- a map from a file or URL, or from text.

MAX_PAC_BYTES = 1024 * 1024 module-attribute

__all__ = ('functions', 'PacScript', 'PacProxyMap', 'load', 'loads', 'MAX_PAC_BYTES') module-attribute

functions = _MappingProxyType({'dnsResolve': dnsResolve, 'dnsResolveEx': dnsResolveEx, 'myIpAddress': myIpAddress, 'myIpAddressEx': myIpAddressEx, 'dnsDomainLevels': dnsDomainLevels, 'convert_addr': convert_addr, 'shExpMatch': shExpMatch, 'weekdayRange': weekdayRange, 'dateRange': dateRange, 'timeRange': timeRange, 'isPlainHostName': isPlainHostName, 'dnsDomainIs': dnsDomainIs, 'localHostOrDomainIs': localHostOrDomainIs, 'isResolvable': isResolvable, 'isResolvableEx': isResolvableEx, 'isInNet': isInNet, 'isInNetEx': isInNetEx, 'sortIpAddressList': sortIpAddressList, 'getClientVersion': getClientVersion}) module-attribute

Every PAC builtin by its PAC name; read-only.

PacProxyMap(script, *, browser_compatibility=False)

A proxy map that asks FindProxyForURL(url, host) for every URL.

script is a :class:PacScript or a Python callable with the same signature, which takes the place of subclassing. host is the URL's host, lower-cased. The result string is read with :meth:Proxy.parse_pac_result; a PAC always answers, so this map never raises KeyError: whatever a Python function raises becomes :class:~proxylib.PACError, as a script error does.

browser_compatibility=True passes an https URL without its path and query, as Chrome and Firefox do so a PAC server cannot see them.

Source code in src/proxylib/pac/map.py
33
34
35
36
37
def __init__(
    self, script: "Union[PacScript, FindProxyForURL]", *, browser_compatibility: bool = False
) -> None:
    self.script = script
    self.browser_compatibility = browser_compatibility

__slots__ = ('script', 'browser_compatibility') class-attribute instance-attribute

browser_compatibility = browser_compatibility instance-attribute

script = script instance-attribute

__getitem__(url)

Source code in src/proxylib/pac/map.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __getitem__(self, url: str) -> "Tuple[Optional[Proxy], ...]":
    parts = urlsplit(url)
    if self.browser_compatibility and parts.scheme.lower() == "https":
        url = urlunsplit((parts.scheme, parts.netloc, "/", "", ""))
    host = parts.hostname or ""
    if isinstance(self.script, PacScript):
        result = self.script.find_proxy_for_url(url, host)
    else:
        try:
            result = self.script(url, host)
        except Exception as exc:
            raise PACError(f"FindProxyForURL raised {exc!r}") from exc
    if not isinstance(result, str):
        return (None,)
    return Proxy.parse_pac_result(result)

__repr__()

Source code in src/proxylib/pac/map.py
39
40
def __repr__(self) -> str:
    return f"PacProxyMap({self.script!r})"

close()

End the script's helper process (a Python function has none).

Source code in src/proxylib/pac/map.py
42
43
44
45
46
def close(self) -> None:
    """End the script's helper process (a Python function has none)."""
    close = getattr(self.script, "close", None)
    if callable(close):
        close()

PacScript(source, *, engine=None, functions=None, timeout=5.0, on_missing_engine='raise')

A PAC script, ready to answer FindProxyForURL.

source is the script text. engine names the JS engine (default: PROXYLIB_JS_ENGINE, else quickjs, else dukpy; see :mod:proxylib.pac.engines). functions adds to or overrides the PAC builtins the script can call; the script runs in a helper process, so they must be picklable module-level functions it can import. timeout bounds each call in seconds (the builtins' own DNS lookups are bounded at 2 s each): a call that overruns raises :class:PACError, its helper process is ended, and for the next 30 seconds every call raises :class:PACError at once; after that the script is started afresh. A script that overruns while loading is not loaded.

With no engine installed, on_missing_engine="raise" (the default) raises :class:MissingExtraError; "direct" warns and the script answers "DIRECT" to everything.

A script error, a script that does not define FindProxyForURL and an overrun all raise :class:PACError. Safe to share between threads; calls are serialised. close() releases the engine (garbage collection does too).

Source code in src/proxylib/pac/script.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def __init__(
    self,
    source: str,
    *,
    engine: "Optional[str]" = None,
    functions: "Optional[Mapping[str, Callable[..., Any]]]" = None,
    timeout: float = 5.0,
    on_missing_engine: str = "raise",
) -> None:
    if on_missing_engine not in ("raise", "direct"):
        raise ValueError("on_missing_engine must be 'raise' or 'direct'")
    if "FindProxyForURL" not in source:
        raise PACError("the script defines no FindProxyForURL")
    self.source = source
    self.timeout = timeout
    self.engine_name: "Optional[str]" = None
    self._failed: "Optional[str]" = None
    self._retry_at = 0.0
    self._functions = dict(functions or {})
    self._restart_lock = threading.Lock()
    self._runner: "Optional[_Runner]" = None

    engine_cls = engines.get_engine(engine)
    if engine_cls is None:
        if on_missing_engine == "raise":
            raise MissingExtraError("Running a PAC script", name="quickjs", extra="pac")
        warnings.warn(
            'no PAC JavaScript engine is installed (pip install "proxylib[pac]" '
            'or "proxylib[dukpy]"); this PAC answers DIRECT to everything',
            stacklevel=2,
        )
        return
    self.engine_name = engine_cls.name
    try:
        self._runner = _Runner(engine_cls.name, source, self._functions, timeout)
    except _Overrun:
        raise PACError(f"the PAC script ran longer than {timeout} s while loading") from None

engine_name = engine_cls.name instance-attribute

source = source instance-attribute

timeout = timeout instance-attribute

__del__()

Source code in src/proxylib/pac/script.py
198
199
def __del__(self) -> None:
    self.close()

__repr__()

Source code in src/proxylib/pac/script.py
206
207
208
def __repr__(self) -> str:
    state = "failed" if self._failed else (self.engine_name or "no engine")
    return f"<PacScript {state}, {len(self.source)} chars>"

close()

Source code in src/proxylib/pac/script.py
201
202
203
204
def close(self) -> None:
    runner = getattr(self, "_runner", None)
    if runner is not None:
        runner.close()

find_proxy_for_url(url, host)

Run FindProxyForURL(url, host). A result that is not a string is "DIRECT", as browsers treat it.

Source code in src/proxylib/pac/script.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def find_proxy_for_url(self, url: str, host: str) -> str:
    """Run ``FindProxyForURL(url, host)``. A result that is not a string
    is ``"DIRECT"``, as browsers treat it."""
    if self._runner is None:
        return "DIRECT"
    if self._failed:
        self._restart()
    runner = self._runner
    try:
        result = runner.call(url, host)
    except _Overrun:
        self._failed = f"it ran longer than {self.timeout} s"
        self._retry_at = time.monotonic() + _COOLDOWN
        runner.close()
        raise PACError(f"the PAC script ran longer than {self.timeout} s") from None
    return result if isinstance(result, str) else "DIRECT"

from_file(path, *, encoding='utf-8-sig', **options) classmethod

Read a script from a file. options are those of PacScript.

Source code in src/proxylib/pac/script.py
240
241
242
243
244
245
@classmethod
def from_file(
    cls, path: "Union[str, os.PathLike]", *, encoding: str = "utf-8-sig", **options: Any
) -> "PacScript":
    """Read a script from a file. ``options`` are those of ``PacScript``."""
    return cls(Path(path).read_text(encoding=encoding), **options)

from_url(url, *, timeout=10.0, max_bytes=MAX_PAC_BYTES, cache_ttl=300.0, **options) classmethod

Download a script, never through a proxy. timeout bounds the download; a body over max_bytes raises :class:PACError. A file:// URL is read directly and never cached; downloaded text is kept for cache_ttl seconds (0/None: no cache). options are those of PacScript, whose timeout is spelled script_timeout here.

Source code in src/proxylib/pac/script.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@classmethod
def from_url(
    cls,
    url: str,
    *,
    timeout: float = 10.0,
    max_bytes: int = MAX_PAC_BYTES,
    cache_ttl: "Optional[float]" = 300.0,
    **options: Any,
) -> "PacScript":
    """Download a script, never through a proxy. ``timeout`` bounds the
    download; a body over ``max_bytes`` raises :class:`PACError`. A
    ``file://`` URL is read directly and never cached; downloaded text is
    kept for ``cache_ttl`` seconds (``0``/``None``: no cache). ``options``
    are those of ``PacScript``, whose ``timeout`` is spelled
    ``script_timeout`` here.
    """
    if "script_timeout" in options:
        options["timeout"] = options.pop("script_timeout")
    return cls(_fetch(url, timeout=timeout, max_bytes=max_bytes, cache_ttl=cache_ttl), **options)

load(location, *, timeout=10.0, cache_ttl=300.0, script_timeout=5.0, engine=None, on_missing_engine='raise')

A :class:PacProxyMap for the PAC file at location: a URL (http, https, file) or a path. Never script text -- use :func:loads. A file:path without // is a path.

timeout, cache_ttl: the download (see :meth:PacScript.from_url). script_timeout, engine, on_missing_engine: the script (see :class:PacScript).

Source code in src/proxylib/pac/__init__.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def load(
    location: "Union[str, os.PathLike]",
    *,
    timeout: float = 10.0,
    cache_ttl: "Optional[float]" = 300.0,
    script_timeout: float = 5.0,
    engine: "Optional[str]" = None,
    on_missing_engine: str = "raise",
) -> PacProxyMap:
    """A :class:`PacProxyMap` for the PAC file at ``location``: a URL
    (``http``, ``https``, ``file``) or a path. Never script text -- use
    :func:`loads`. A ``file:path`` without ``//`` is a path.

    ``timeout``, ``cache_ttl``: the download (see :meth:`PacScript.from_url`).
    ``script_timeout``, ``engine``, ``on_missing_engine``: the script (see
    :class:`PacScript`).
    """
    options = {"engine": engine, "on_missing_engine": on_missing_engine}
    if isinstance(location, str) and "://" in location:
        script = PacScript.from_url(
            location, timeout=timeout, cache_ttl=cache_ttl, script_timeout=script_timeout, **options
        )
    else:
        if isinstance(location, str) and location.startswith("file:"):
            location = location[len("file:"):]
        script = PacScript.from_file(location, timeout=script_timeout, **options)
    return PacProxyMap(script)

loads(text, *, script_timeout=5.0, engine=None, on_missing_engine='raise')

A :class:PacProxyMap for PAC script text.

Source code in src/proxylib/pac/__init__.py
53
54
55
56
57
58
59
60
61
62
63
def loads(
    text: str,
    *,
    script_timeout: float = 5.0,
    engine: "Optional[str]" = None,
    on_missing_engine: str = "raise",
) -> PacProxyMap:
    """A :class:`PacProxyMap` for PAC script text."""
    return PacProxyMap(
        PacScript(text, timeout=script_timeout, engine=engine, on_missing_engine=on_missing_engine)
    )

proxylib.pac.script

PacScript: one PAC script running in its own JavaScript engine.

MAX_PAC_BYTES = 1024 * 1024 module-attribute

__all__ = ('PacScript', 'MAX_PAC_BYTES') module-attribute

PacScript(source, *, engine=None, functions=None, timeout=5.0, on_missing_engine='raise')

A PAC script, ready to answer FindProxyForURL.

source is the script text. engine names the JS engine (default: PROXYLIB_JS_ENGINE, else quickjs, else dukpy; see :mod:proxylib.pac.engines). functions adds to or overrides the PAC builtins the script can call; the script runs in a helper process, so they must be picklable module-level functions it can import. timeout bounds each call in seconds (the builtins' own DNS lookups are bounded at 2 s each): a call that overruns raises :class:PACError, its helper process is ended, and for the next 30 seconds every call raises :class:PACError at once; after that the script is started afresh. A script that overruns while loading is not loaded.

With no engine installed, on_missing_engine="raise" (the default) raises :class:MissingExtraError; "direct" warns and the script answers "DIRECT" to everything.

A script error, a script that does not define FindProxyForURL and an overrun all raise :class:PACError. Safe to share between threads; calls are serialised. close() releases the engine (garbage collection does too).

Source code in src/proxylib/pac/script.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def __init__(
    self,
    source: str,
    *,
    engine: "Optional[str]" = None,
    functions: "Optional[Mapping[str, Callable[..., Any]]]" = None,
    timeout: float = 5.0,
    on_missing_engine: str = "raise",
) -> None:
    if on_missing_engine not in ("raise", "direct"):
        raise ValueError("on_missing_engine must be 'raise' or 'direct'")
    if "FindProxyForURL" not in source:
        raise PACError("the script defines no FindProxyForURL")
    self.source = source
    self.timeout = timeout
    self.engine_name: "Optional[str]" = None
    self._failed: "Optional[str]" = None
    self._retry_at = 0.0
    self._functions = dict(functions or {})
    self._restart_lock = threading.Lock()
    self._runner: "Optional[_Runner]" = None

    engine_cls = engines.get_engine(engine)
    if engine_cls is None:
        if on_missing_engine == "raise":
            raise MissingExtraError("Running a PAC script", name="quickjs", extra="pac")
        warnings.warn(
            'no PAC JavaScript engine is installed (pip install "proxylib[pac]" '
            'or "proxylib[dukpy]"); this PAC answers DIRECT to everything',
            stacklevel=2,
        )
        return
    self.engine_name = engine_cls.name
    try:
        self._runner = _Runner(engine_cls.name, source, self._functions, timeout)
    except _Overrun:
        raise PACError(f"the PAC script ran longer than {timeout} s while loading") from None

engine_name = engine_cls.name instance-attribute

source = source instance-attribute

timeout = timeout instance-attribute

__del__()

Source code in src/proxylib/pac/script.py
198
199
def __del__(self) -> None:
    self.close()

__repr__()

Source code in src/proxylib/pac/script.py
206
207
208
def __repr__(self) -> str:
    state = "failed" if self._failed else (self.engine_name or "no engine")
    return f"<PacScript {state}, {len(self.source)} chars>"

close()

Source code in src/proxylib/pac/script.py
201
202
203
204
def close(self) -> None:
    runner = getattr(self, "_runner", None)
    if runner is not None:
        runner.close()

find_proxy_for_url(url, host)

Run FindProxyForURL(url, host). A result that is not a string is "DIRECT", as browsers treat it.

Source code in src/proxylib/pac/script.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def find_proxy_for_url(self, url: str, host: str) -> str:
    """Run ``FindProxyForURL(url, host)``. A result that is not a string
    is ``"DIRECT"``, as browsers treat it."""
    if self._runner is None:
        return "DIRECT"
    if self._failed:
        self._restart()
    runner = self._runner
    try:
        result = runner.call(url, host)
    except _Overrun:
        self._failed = f"it ran longer than {self.timeout} s"
        self._retry_at = time.monotonic() + _COOLDOWN
        runner.close()
        raise PACError(f"the PAC script ran longer than {self.timeout} s") from None
    return result if isinstance(result, str) else "DIRECT"

from_file(path, *, encoding='utf-8-sig', **options) classmethod

Read a script from a file. options are those of PacScript.

Source code in src/proxylib/pac/script.py
240
241
242
243
244
245
@classmethod
def from_file(
    cls, path: "Union[str, os.PathLike]", *, encoding: str = "utf-8-sig", **options: Any
) -> "PacScript":
    """Read a script from a file. ``options`` are those of ``PacScript``."""
    return cls(Path(path).read_text(encoding=encoding), **options)

from_url(url, *, timeout=10.0, max_bytes=MAX_PAC_BYTES, cache_ttl=300.0, **options) classmethod

Download a script, never through a proxy. timeout bounds the download; a body over max_bytes raises :class:PACError. A file:// URL is read directly and never cached; downloaded text is kept for cache_ttl seconds (0/None: no cache). options are those of PacScript, whose timeout is spelled script_timeout here.

Source code in src/proxylib/pac/script.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@classmethod
def from_url(
    cls,
    url: str,
    *,
    timeout: float = 10.0,
    max_bytes: int = MAX_PAC_BYTES,
    cache_ttl: "Optional[float]" = 300.0,
    **options: Any,
) -> "PacScript":
    """Download a script, never through a proxy. ``timeout`` bounds the
    download; a body over ``max_bytes`` raises :class:`PACError`. A
    ``file://`` URL is read directly and never cached; downloaded text is
    kept for ``cache_ttl`` seconds (``0``/``None``: no cache). ``options``
    are those of ``PacScript``, whose ``timeout`` is spelled
    ``script_timeout`` here.
    """
    if "script_timeout" in options:
        options["timeout"] = options.pop("script_timeout")
    return cls(_fetch(url, timeout=timeout, max_bytes=max_bytes, cache_ttl=cache_ttl), **options)

proxylib.pac.map

PacProxyMap: a proxy map answered by a PAC script or a Python function.

FindProxyForURL = Callable[[str, str], str] module-attribute

__all__ = ('PacProxyMap',) module-attribute

PacProxyMap(script, *, browser_compatibility=False)

A proxy map that asks FindProxyForURL(url, host) for every URL.

script is a :class:PacScript or a Python callable with the same signature, which takes the place of subclassing. host is the URL's host, lower-cased. The result string is read with :meth:Proxy.parse_pac_result; a PAC always answers, so this map never raises KeyError: whatever a Python function raises becomes :class:~proxylib.PACError, as a script error does.

browser_compatibility=True passes an https URL without its path and query, as Chrome and Firefox do so a PAC server cannot see them.

Source code in src/proxylib/pac/map.py
33
34
35
36
37
def __init__(
    self, script: "Union[PacScript, FindProxyForURL]", *, browser_compatibility: bool = False
) -> None:
    self.script = script
    self.browser_compatibility = browser_compatibility

__slots__ = ('script', 'browser_compatibility') class-attribute instance-attribute

browser_compatibility = browser_compatibility instance-attribute

script = script instance-attribute

__getitem__(url)

Source code in src/proxylib/pac/map.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __getitem__(self, url: str) -> "Tuple[Optional[Proxy], ...]":
    parts = urlsplit(url)
    if self.browser_compatibility and parts.scheme.lower() == "https":
        url = urlunsplit((parts.scheme, parts.netloc, "/", "", ""))
    host = parts.hostname or ""
    if isinstance(self.script, PacScript):
        result = self.script.find_proxy_for_url(url, host)
    else:
        try:
            result = self.script(url, host)
        except Exception as exc:
            raise PACError(f"FindProxyForURL raised {exc!r}") from exc
    if not isinstance(result, str):
        return (None,)
    return Proxy.parse_pac_result(result)

__repr__()

Source code in src/proxylib/pac/map.py
39
40
def __repr__(self) -> str:
    return f"PacProxyMap({self.script!r})"

close()

End the script's helper process (a Python function has none).

Source code in src/proxylib/pac/map.py
42
43
44
45
46
def close(self) -> None:
    """End the script's helper process (a Python function has none)."""
    close = getattr(self.script, "close", None)
    if callable(close):
        close()

proxylib.pac.wpad

DNS + HTTP WPAD (Web Proxy Auto-Discovery), which ProxyConfig.to_proxymap() runs when the OS asks for auto-detection.

DHCP option 252 discovery is intentionally not implemented: it needs raw access to the OS's DHCP lease data, which has no portable stdlib-only path.

__all__ = ('discover',) module-attribute

discover(fqdn=None, *, timeout=3.0, cache_ttl=300.0, on_missing_engine='raise')

Try each http://wpad.<domain>/wpad.dat from most to least specific.

Returns a :class:PacProxyMap for the first PAC that loads, or None. A domain whose PAC fails to download or to run is skipped; with no JS engine installed, :class:MissingExtraError is raised unless on_missing_engine="direct". Results, failures included, are cached per fqdn for cache_ttl seconds (0/None: probe afresh).

Source code in src/proxylib/pac/wpad.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def discover(
    fqdn: "Optional[str]" = None,
    *,
    timeout: float = 3.0,
    cache_ttl: "Optional[float]" = 300.0,
    on_missing_engine: str = "raise",
) -> "Optional[PacProxyMap]":
    """Try each ``http://wpad.<domain>/wpad.dat`` from most to least specific.

    Returns a :class:`PacProxyMap` for the first PAC that loads, or ``None``.
    A domain whose PAC fails to download or to run is skipped; with no JS
    engine installed, :class:`MissingExtraError` is raised unless
    ``on_missing_engine="direct"``. Results, failures included, are cached
    per fqdn for ``cache_ttl`` seconds (``0``/``None``: probe afresh).
    """
    fqdn = fqdn or socket.getfqdn()
    if cache_ttl:
        cached = _cache.get(fqdn)
        if cached is not None and (time.monotonic() - cached[0]) < cache_ttl:
            return cached[1]
    result = _discover(fqdn, timeout, on_missing_engine)
    if cache_ttl:
        _cache[fqdn] = (time.monotonic(), result)
    return result

proxylib.pac.engines

PAC JavaScript engines.

Two are supported: quickjs (proxylib[pac]; closer to the spec, has time and memory limits) and dukpy (proxylib[dukpy]; a wheel exists where quickjs has none, such as Windows ARM64). Neither is imported until a script needs it.

Which one runs: the engine= argument, else the PROXYLIB_JS_ENGINE environment variable (a comma-separated priority list such as "dukpy,quickjs"), else quickjs, then dukpy. An unknown name is a ValueError.

__all__ = ('JSEngine', 'names', 'get_engine') module-attribute

JSEngine

Bases: Protocol

One JavaScript execution context. Not thread-safe: PacScript calls it from a single thread.

call(name, *args)

Call the JS global function name with args.

name is resolved in the global scope on every call, so a script that redefines an exported function overrides it.

Source code in src/proxylib/pac/engines/_base.py
27
28
29
30
31
32
33
def call(self, name: str, *args: Any) -> Any:
    """Call the JS global function ``name`` with ``args``.

    ``name`` is resolved in the global scope on every call, so a script
    that redefines an exported function overrides it.
    """
    ...

eval(code)

Evaluate a script (the PAC source, once).

Source code in src/proxylib/pac/engines/_base.py
23
24
25
def eval(self, code: str) -> Any:
    """Evaluate a script (the PAC source, once)."""
    ...

export_function(name, func)

Make func callable from JS as the global function name.

Source code in src/proxylib/pac/engines/_base.py
19
20
21
def export_function(self, name: str, func: "Callable[..., Any]") -> None:
    """Make ``func`` callable from JS as the global function ``name``."""
    ...

set_memory_limit(limit)

Cap the context's memory at limit bytes where the engine supports it; an engine without one ignores the call. (Time is bounded from outside: the script runs in a helper process.)

Source code in src/proxylib/pac/engines/_base.py
35
36
37
38
39
def set_memory_limit(self, limit: int) -> None:
    """Cap the context's memory at ``limit`` bytes where the engine
    supports it; an engine without one ignores the call. (Time is bounded
    from outside: the script runs in a helper process.)"""
    ...

get_engine(engine=None)

The engine class to use, or None when none of the wanted engines is installed.

Source code in src/proxylib/pac/engines/__init__.py
58
59
60
61
62
63
64
def get_engine(engine: "Optional[str]" = None) -> "Optional[Type[JSEngine]]":
    """The engine class to use, or ``None`` when none of the wanted engines
    is installed."""
    for name in _priority(engine):
        if _installed(name):
            return _load(name)
    return None

names()

The engines installed here, in default priority order.

Source code in src/proxylib/pac/engines/__init__.py
36
37
38
def names() -> "Tuple[str, ...]":
    """The engines installed here, in default priority order."""
    return tuple(name for name in _DEFAULT_PRIORITY if _installed(name))