Skip to content

credentials

lab.connectome.credentials

Saving a secret where the project reads it.

The CAVE token is saved by lab.connectome.fetch.save_cave_token; the other secrets are variables in the gitignored config.ENV.

check_env_value(key, value)

Raise unless a variable can be written as one plain line.

Parameters:

Name Type Description Default
key str

Variable name in capitals, digits, and underscores.

required
value str

Letters, digits, and ._~+/=:@%,- only.

required

Raises:

Type Description
ValueError

For a key or value outside those characters.

Examples:

>>> check_env_value("TOKEN", "a1:b2@c3")
Source code in lab/connectome/credentials.py
def check_env_value(key: str, value: str) -> None:
    """Raise unless a variable can be written as one plain line.

    Parameters
    ----------
    key : str
        Variable name in capitals, digits, and underscores.
    value : str
        Letters, digits, and ``._~+/=:@%,-`` only.

    Raises
    ------
    ValueError
        For a `key` or `value` outside those characters.

    Examples
    --------
    >>> check_env_value("TOKEN", "a1:b2@c3")
    """
    if not KEY.fullmatch(key):
        msg = f"{key!r} is not a variable name"
        raise ValueError(msg)
    if not VALUE.fullmatch(value):
        msg = (
            "the value has a character other than letters, digits, "
            "and . _ ~ + / = : @ % , -"
        )
        raise ValueError(msg)

set_env_value(path, key, value)

Set one variable of a .env file and keep every other line.

An existing line for key is replaced and any later one for it is dropped; otherwise the line is added at the end. The file is created readable by its owner only, and a file that exists keeps its mode.

Parameters:

Name Type Description Default
path Path

The file; it is created when missing.

required
key str

Variable name in capitals, digits, and underscores.

required
value str

Letters, digits, and ._~+/=:@%,- only, so it is one word in any dotenv reader.

required

Raises:

Type Description
ValueError

For a key or value outside those characters.

Examples:

>>> import tempfile
>>> path = Path(tempfile.mkdtemp()) / ".env"
>>> _ = path.write_text("A=1\nTOKEN=old\n# kept\n")
>>> set_env_value(path, "TOKEN", "new")
>>> print(path.read_text(), end="")
A=1
TOKEN=new
# kept
>>> set_env_value(path, "OTHER", "x")
>>> path.read_text().splitlines()[-1]
'OTHER=x'
Source code in lab/connectome/credentials.py
def set_env_value(path: Path, key: str, value: str) -> None:
    r"""Set one variable of a ``.env`` file and keep every other line.

    An existing line for `key` is replaced and any later one for it is
    dropped; otherwise the line is added at the end.
    The file is created readable by its owner only, and a file that
    exists keeps its mode.

    Parameters
    ----------
    path : pathlib.Path
        The file; it is created when missing.
    key : str
        Variable name in capitals, digits, and underscores.
    value : str
        Letters, digits, and ``._~+/=:@%,-`` only, so it is one word in
        any dotenv reader.

    Raises
    ------
    ValueError
        For a `key` or `value` outside those characters.

    Examples
    --------
    >>> import tempfile
    >>> path = Path(tempfile.mkdtemp()) / ".env"
    >>> _ = path.write_text("A=1\nTOKEN=old\n# kept\n")
    >>> set_env_value(path, "TOKEN", "new")
    >>> print(path.read_text(), end="")
    A=1
    TOKEN=new
    # kept
    >>> set_env_value(path, "OTHER", "x")
    >>> path.read_text().splitlines()[-1]
    'OTHER=x'
    """
    check_env_value(key, value)
    lines = path.read_text().splitlines() if path.exists() else []
    own = re.compile(rf"\s*(export\s+)?{key}\s*=")
    kept: list[str] = []
    placed = False
    for line in lines:
        if not own.match(line):
            kept.append(line)
        elif not placed:
            kept.append(f"{key}={value}")
            placed = True
    if not placed:
        kept.append(f"{key}={value}")
    mode = path.stat().st_mode & 0o777 if path.exists() else 0o600
    temporary = path.with_name(f"{path.name}.tmp")
    descriptor = os.open(
        temporary, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode
    )
    with os.fdopen(descriptor, "w") as file:
        file.write("\n".join(kept) + "\n")
    temporary.replace(path)