Skip to content

Add BROWSERS dict and browsers= parameter to load()#230

Open
bwagner wants to merge 1 commit intoborisbabic:masterfrom
bwagner:feature/browser-list
Open

Add BROWSERS dict and browsers= parameter to load()#230
bwagner wants to merge 1 commit intoborisbabic:masterfrom
bwagner:feature/browser-list

Conversation

@bwagner
Copy link
Copy Markdown

@bwagner bwagner commented Feb 20, 2026

Add BROWSERS dict and browser subset support to load()

Summary

Two small, purely additive changes that give consumers explicit control over
which browsers are involved — without breaking any existing code.

  1. BROWSERS dict — a public name → callable mapping built from the
    existing (but function-only) all_browsers list.
  2. browsers parameter on load() — lets callers pass a subset of
    browsers rather than being forced into all-or-one.

Motivation

1. Enumerating supported browsers is currently fragile

Downstream tools that need a name → function mapping (e.g. for CLI choices=,
input validation, or dynamic dispatch) have to introspect the module:

BROWSERS = {
    name: fn
    for name in dir(browser_cookie3)
    if name.islower()
    and not name.startswith("_")
    and callable(fn := getattr(browser_cookie3, name))
    and name not in ("load", "create_cookie")
}

This silently breaks when helper functions are added. As of the current
release, open_dbus_connection and unpad are already false-positives.

all_browsers already exists and is exported, but it's a list of callables —
you can't look up a browser by name without scanning it.

2. load() is all-or-nothing

You can call a specific browser function directly, or use load() for all of
them, but there's no way to say "firefox and chrome only". This matters when:

  • you know which browsers the user has installed and want to skip the noise
    from BrowserCookieErrors on absent ones
  • you're building a tool where the user has selected a set of browsers

Changes

browser_cookie3/__init__.py

Add BROWSERS immediately after all_browsers, and add it to __all__:

# Existing (unchanged)
all_browsers = [chrome, chromium, opera, opera_gx, brave, edge, vivaldi,
                firefox, librewolf, safari, lynx, w3m, arc]

# New: public name → loader mapping
BROWSERS: dict[str, Callable] = {fn.__name__: fn for fn in all_browsers}

Update __all__:

__all__ = [
    'BrowserCookieError',
    'load',
    'all_browsers',
    'BROWSERS',       # <-- added
    *all_browsers,
]

Add an optional browsers parameter to load():

def load(domain_name: str = "", browsers=None):
    """Try to load cookies from supported browsers and return combined cookiejar.

    Parameters
    ----------
    domain_name:
        Only load cookies from this domain (passed through to each browser).
    browsers:
        Iterable of browser names (str) or callables to try, e.g.
        ``['firefox', 'chrome']`` or ``[browser_cookie3.firefox]``.
        Defaults to ``None``, which tries all browsers (existing behaviour).
    """
    if browsers is None:
        fns = all_browsers
    else:
        fns = [BROWSERS[b] if isinstance(b, str) else b for b in browsers]

    cj = http.cookiejar.CookieJar()
    for cookie_fn in fns:
        try:
            for cookie in cookie_fn(domain_name=domain_name):
                cj.set_cookie(cookie)
        except BrowserCookieError:
            pass
    return cj

Usage examples

Programmatic enumeration

import browser_cookie3

# Validate user-supplied browser name
if user_input not in browser_cookie3.BROWSERS:
    raise ValueError(f"Unknown browser: {user_input!r}. "
                     f"Choose from: {list(browser_cookie3.BROWSERS)}")

# Dispatch by name
cj = browser_cookie3.BROWSERS[user_input](domain_name="example.com")

CLI integration (argparse)

parser.add_argument(
    "--browser",
    choices=browser_cookie3.BROWSERS,   # validates input + powers tab-completion
    action="append",
    dest="browsers",
    metavar="BROWSER",
    help="Browser(s) to load cookies from (default: all).",
)

cj = browser_cookie3.load(
    domain_name=args.domain,
    browsers=args.browsers,   # None means all; a list means subset
)

Subset loading

# Only Firefox and Chrome; skip everything else
cj = browser_cookie3.load(domain_name="example.com", browsers=["firefox", "chrome"])

# Mix of names and callables — both work
cj = browser_cookie3.load(browsers=["firefox", browser_cookie3.chrome])

Backwards compatibility

  • all_browsers is unchanged.
  • load() with no arguments behaves identically to before.
  • BROWSERS is a new export; nothing is removed or renamed.
  • No new dependencies.

Notes

  • BROWSERS stays in sync with all_browsers automatically — no manual
    maintenance needed when a new browser is added.
  • Passing an unknown string to browsers= raises a KeyError from the dict
    lookup, which is clear enough; callers doing input validation via
    choices=BROWSERS will never reach it with an invalid value anyway.
  • The isinstance(b, str) branch in load() means callers who already hold
    a function reference (e.g. browser_cookie3.firefox) can pass it directly,
    consistent with how all_browsers works today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant