Skip to content

Installed Modules

installed_modules

Read/write convention for the installed_modules.txt file at the project root.

The file format is one module name per non-comment line. Two optional header lines are supported:

# generated_at: 2026-05-09T11:46:14Z
# generated_by: <free-form>

Other # lines are treated as plain comments and ignored.

Classes:

Name Description
InstalledModules

Contents and metadata of an installed_modules.txt file.

Functions:

Name Description
installed_modules_path

Return the conventional path of the installed-modules file.

read_installed_modules

Read installed_modules.txt at the project root.

InstalledModules dataclass

InstalledModules(modules: list[str], generated_at: datetime | None, generated_by: str | None, path: Path)

Contents and metadata of an installed_modules.txt file.

Attributes:

Name Type Description
modules list[str]

Ordered, deduplicated list of module names.

generated_at datetime | None

Timestamp parsed from the # generated_at: header; falls back to the file's mtime when the header is absent.

generated_by str | None

Free-form string from the # generated_by: header, or None when the header is absent.

path Path

Absolute path of the file.

installed_modules_path

installed_modules_path(repo_root: Path) -> Path

Return the conventional path of the installed-modules file.

Parameters:

Name Type Description Default

repo_root

Path

Repository root directory.

required

Returns:

Type Description
Path

Path to the installed-modules file (may not exist yet).

Source code in src/oops/io/installed_modules.py
def installed_modules_path(repo_root: Path) -> Path:
    """Return the conventional path of the installed-modules file.

    Args:
        repo_root: Repository root directory.

    Returns:
        Path to the installed-modules file (may not exist yet).
    """
    return repo_root / config.project.file_installed_modules

read_installed_modules

read_installed_modules(repo_root: Path) -> InstalledModules | None

Read installed_modules.txt at the project root.

Parameters:

Name Type Description Default

repo_root

Path

Repository root directory.

required

Returns:

Type Description
InstalledModules | None

Parsed InstalledModules instance, or None when the file does not

InstalledModules | None

exist. The caller decides whether absence is fatal or informational.

Header lines accepted (case-sensitive)::

# generated_at: 2026-05-09T11:46:14Z
# generated_by: <free-form>

Other # lines are treated as plain comments and ignored. Blank lines are ignored. One module name per non-comment line.

Source code in src/oops/io/installed_modules.py
def read_installed_modules(repo_root: Path) -> InstalledModules | None:
    """Read installed_modules.txt at the project root.

    Args:
        repo_root: Repository root directory.

    Returns:
        Parsed ``InstalledModules`` instance, or ``None`` when the file does not
        exist. The caller decides whether absence is fatal or informational.

    Header lines accepted (case-sensitive)::

        # generated_at: 2026-05-09T11:46:14Z
        # generated_by: <free-form>

    Other ``#`` lines are treated as plain comments and ignored.
    Blank lines are ignored. One module name per non-comment line.
    """
    path = installed_modules_path(repo_root)
    if not path.exists():
        return None

    generated_at: datetime | None = None
    generated_by: str | None = None
    seen: dict[str, None] = {}  # insertion-order dedup via dict

    for raw_line in path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line:
            continue
        if line.startswith("#"):
            if line.startswith("# generated_at:"):
                value = line[len("# generated_at:"):].strip()
                try:
                    generated_at = datetime.fromisoformat(value.replace("Z", "+00:00"))
                except ValueError:
                    generated_at = None
            elif line.startswith("# generated_by:"):
                generated_by = line[len("# generated_by:"):].strip()
            continue
        seen[line] = None

    if generated_at is None:
        generated_at = datetime.fromtimestamp(path.stat().st_mtime, tz=timezone.utc)

    return InstalledModules(
        modules=list(seen.keys()),
        generated_at=generated_at,
        generated_by=generated_by,
        path=path,
    )