Skip to content

hpcman.software.db

Maintains software sqlite database including creation and update, using sqlmodel package

Reference: https://sqlmodel.tiangolo.com

Classes

Software

Bases: SQLModel

Source code in hpcman/hpcman/software/db.py
class Software(SQLModel, table=True):  # type: ignore[call-arg]
    __table_args__ = (UniqueConstraint("name", "arch", name="unique_software"),)
    model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)  # type: ignore
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    arch: CPUArch = Field(sa_type=ArchSQLStr)
    versions: list["InstalledVersion"] = Relationship(back_populates="software")
    updated: datetime
    latest: VerStr = Field(sa_type=VerSQLStr)
    provides: list[str] | None = Field(sa_type=StrListSQLStr, nullable=True)
    libs: list[str] | None = Field(sa_type=StrListSQLStr, nullable=True)
    tags: list[str] | None = Field(sa_type=StrListSQLStr, nullable=True)
    category: Category = Field(sa_type=CategorySQLStr)
    docs: list[AnyUrl] | None = Field(sa_type=URLListSQLStr, nullable=True)
    refs: list[AnyUrl] | None = Field(sa_type=URLListSQLStr, nullable=True)
    version_in_path: VerStr | None = Field(sa_type=VerSQLStr, nullable=True)

Fields

Name Type Description
id int | None
name str
arch CPUArch
versions list[InstalledVersion]
updated datetime
latest VerStr
provides list[str] | None
libs list[str] | None
tags list[str] | None
category Category
docs list[AnyUrl] | None
refs list[AnyUrl] | None
version_in_path VerStr | None

InstalledVersion

Bases: SQLModel

Source code in hpcman/hpcman/software/db.py
class InstalledVersion(SQLModel, table=True):  # type: ignore[call-arg]
    model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)  # type: ignore
    id: int | None = Field(default=None, primary_key=True)
    version: VerStr = Field(sa_type=VerSQLStr)
    software_id: int | None = Field(default=None, foreign_key="software.id")
    software: Software = Relationship(back_populates="versions")
    installed: datetime
    method: InstallMethod = Field(sa_type=MethodSQLStr)
    prefix: Path = Field(sa_type=PathSQLStr)
    exes: list[Path] = Field(sa_type=PathListSQLStr)
    pkgs: list[str] | None = Field(sa_type=StrListSQLStr)
    version_message: str | None
    help_message: str | None
    linked_dir: bool
    linked_exes: bool
    linked_to: Path | None = Field(sa_type=PathSQLStr, nullable=True)

Fields

Name Type Description
id int | None
version VerStr
software_id int | None
software Software
installed datetime
method InstallMethod
prefix Path
exes list[Path]
pkgs list[str] | None
version_message str | None
help_message str | None
linked_dir bool
linked_exes bool
linked_to Path | None

Functions

start_engine

def start_engine(
    db_path: Path | None,
    db_type: Literal['sqlite'] = 'sqlite',
    debug: bool = False
) -> Engine

Creates engine with sqlmodel and returns it

Source code in hpcman/hpcman/software/db.py
def start_engine(db_path: Path | None, db_type: Literal["sqlite"] = "sqlite", debug: bool = False) -> Engine:
    """Creates engine with sqlmodel and returns it"""
    if db_type == "sqlite":
        if db_path is None:
            file_url = "sqlite://"
        else:
            file_url = f"sqlite:///{db_path.resolve()}"
    else:
        rprint(f"Given db_type '{db_type}' is not supported. Only sqlite is allowed.")
        exit(1)

    engine = create_engine(file_url, echo=debug)
    create_db_and_tables(engine)

    return engine

create_db_and_tables

def create_db_and_tables(
    engine: Engine
) -> None
Source code in hpcman/hpcman/software/db.py
def create_db_and_tables(engine: Engine) -> None:
    SQLModel.metadata.create_all(engine)

add_software_row

def add_software_row(
    session: Session,
    name: str,
    updated: datetime,
    version: Version,
    exe_names: list[str],
    tags: list[str],
    category: Category,
    docs: list[AnyUrl] | None,
    refs: list[AnyUrl] | None,
    linked_exes: bool,
    libs: list[str] | None,
    arch: CPUArch
) -> Software
Source code in hpcman/hpcman/software/db.py
def add_software_row(
    session: Session,
    name: str,
    updated: datetime,
    version: Version,
    exe_names: list[str],
    tags: list[str],
    category: Category,
    docs: list[AnyUrl] | None,
    refs: list[AnyUrl] | None,
    linked_exes: bool,
    libs: list[str] | None,
    arch: CPUArch,
) -> Software:
    software = Software(
        name=name,
        updated=updated,
        latest=version,
        arch=arch,
        provides=exe_names,
        libs=libs,
        tags=tags,
        category=category,
        docs=docs,
        refs=refs,
        version_in_path=version if linked_exes else None,
    )

    session.add(software)
    session.commit()
    return software

update_software_row

def update_software_row(
    software: Software,
    version: Version,
    exe_names: list[str],
    libs: list[str] | None,
    tags: list[str] | None,
    category: Category,
    docs: list[AnyUrl] | None,
    refs: list[AnyUrl] | None,
    linkexe: bool,
    arch: CPUArch
) -> Software
Source code in hpcman/hpcman/software/db.py
def update_software_row(
    software: Software,
    version: Version,
    exe_names: list[str],
    libs: list[str] | None,
    tags: list[str] | None,
    category: Category,
    docs: list[AnyUrl] | None,
    refs: list[AnyUrl] | None,
    linkexe: bool,
    arch: CPUArch,
) -> Software:
    if software.arch != arch:
        raise ValueError(f"Architecture mismatch in DB: {software.arch} != {arch} for {software.name}")
    software.latest = version
    software.provides = exe_names
    software.libs = libs
    software.tags = tags
    software.category = category
    software.docs = docs
    software.refs = refs
    if linkexe:
        software.version_in_path = version
    return software

update_software_db

def update_software_db(
    db_path: Path,
    name: str,
    version: Version,
    installed: datetime,
    method: InstallMethod,
    target: Path,
    exe_paths: list[Path],
    libs: list[str] | None,
    ver_msg: str | None,
    help_msg: str | None,
    linked_dir: bool,
    linked_exes: bool,
    bindir: Path,
    exe_names: list[str],
    tags: list[str],
    category: Category,
    docs: list[AnyUrl] | None,
    refs: list[AnyUrl] | None,
    pkgs: list[str] | None,
    arch: CPUArch,
    debug: bool
) -> None
Source code in hpcman/hpcman/software/db.py
def update_software_db(
    db_path: Path,
    name: str,
    version: Version,
    installed: datetime,
    method: InstallMethod,
    target: Path,
    exe_paths: list[Path],
    libs: list[str] | None,
    ver_msg: str | None,
    help_msg: str | None,
    linked_dir: bool,
    linked_exes: bool,
    bindir: Path,
    exe_names: list[str],
    tags: list[str],
    category: Category,
    docs: list[AnyUrl] | None,
    refs: list[AnyUrl] | None,
    pkgs: list[str] | None,
    arch: CPUArch,
    debug: bool,
) -> None:
    engine = start_engine(db_path=db_path, debug=debug)
    with Session(engine) as session:
        software: Software | None = session.exec(
            select(Software).where(Software.name == name).where(Software.arch == arch)
        ).one_or_none()
        if software is None:
            software = add_software_row(
                session=session,
                name=name,
                updated=installed,
                version=version,
                arch=arch,
                exe_names=exe_names,
                tags=tags,
                category=category,
                docs=docs,
                refs=refs,
                linked_exes=linked_exes,
                libs=libs,
            )
        current_version = InstalledVersion(
            version=version,
            installed=installed,
            software_id=software.id,
            method=method,
            prefix=target,
            exes=exe_paths,  # type: ignore
            pkgs=pkgs,
            version_message=ver_msg,
            help_message=help_msg,
            linked_dir=linked_dir,
            linked_exes=linked_exes,
            linked_to=bindir if linked_exes else None,
        )
        for old_version in software.versions:
            if current_version.prefix == old_version.prefix:
                session.delete(old_version)
        software.versions.append(current_version)
        if current_version.version == max([x.version for x in software.versions]):
            software = update_software_row(
                software=software,
                version=version,
                exe_names=exe_names,
                tags=tags,
                category=category,
                docs=docs,
                refs=refs,
                linkexe=linked_exes,
                libs=libs,
                arch=arch,
            )
            for previous_version in software.versions:
                if previous_version.id == current_version.id:
                    continue
                else:
                    if current_version.linked_dir:
                        previous_version.linked_dir = False
                    if current_version.linked_exes:
                        previous_version.linked_exes = False
        session.commit()

list_software

def list_software(
    db_path: Path,
    markdown: bool = False
) -> None
Source code in hpcman/hpcman/software/db.py
def list_software(db_path: Path, markdown: bool = False) -> None:
    engine = start_engine(db_path=db_path)

    headerkeys = [
        "name",
        "arch",
        "latest",
        "updated",
        "versions",
        "provides",
        "tags",
        "category",
    ]
    if markdown:
        headerkeys.extend(["docs", "refs"])

    table = get_table(headerkeys=headerkeys, title="Software List", md=markdown)

    with Session(engine) as session:
        software_list = session.exec(select(Software)).all()
        for software in software_list:
            data = software.model_dump(mode="json")
            versions = []
            for version in software.versions:
                if (cur_ver := version.version) == software.version_in_path:
                    if markdown:
                        versions.append(f"**{cur_ver}\\***")
                    else:
                        versions.append(f"[bold]{cur_ver}*[/bold]")
                else:
                    versions.append(version.version)
            data["versions"] = list_to_csv(versions)
            if len(data["provides"]) > 1:
                data["provides"] = [data["provides"][0], "..."]

            for key in ("provides", "tags"):
                data[key] = list_to_csv(data[key])

            for key in ("docs", "refs"):
                data[key] = list_to_csv(data[key], links=True)

            if markdown:
                data["updated"] = datetime.fromisoformat(data["updated"]).strftime("%Y-%m-%d")
            else:
                data["updated"] = datetime.fromisoformat(data["updated"]).strftime("%a %d %b %Y")
            try:
                table.add_row(*[data.get(x, "") for x in headerkeys])
            except AttributeError:
                table.value_matrix.append([data.get(x, "") for x in headerkeys])

        print_rich_table(table)

list_to_csv

def list_to_csv(
    data: Iterable | None,
    links: bool = False
) -> str | None
Source code in hpcman/hpcman/software/db.py
def list_to_csv(data: Iterable | None, links: bool = False) -> str | None:
    if data is None:
        return None
    if links:
        return ", ".join(f"[link]({x})" for x in data)
    else:
        return ", ".join(str(x) for x in data)

get_table

def get_table(
    headerkeys: list[str],
    title: str,
    md: bool = False
) -> Union[Table, TableWriter]
Source code in hpcman/hpcman/software/db.py
def get_table(headerkeys: list[str], title: str, md: bool = False) -> Union[Table, TableWriter]:
    if md:
        return get_table_writer(headerkeys, title)
    else:
        return get_rich_table(headerkeys, title)

get_rich_table

def get_rich_table(
    headerkeys: list[str],
    title: str
) -> Table
Source code in hpcman/hpcman/software/db.py
def get_rich_table(headerkeys: list[str], title: str) -> Table:
    borders = box.HEAVY_HEAD
    if not sys.stdout.isatty():
        borders = None
    header: list[Column] = [Column(x, overflow="fold") for x in headerkeys]

    table = Table(*header, title=title, box=borders, expand=True)
    return table

get_table_writer

def get_table_writer(
    headerkeys: list[str],
    title: str
) -> TableWriter
Source code in hpcman/hpcman/software/db.py
def get_table_writer(headerkeys: list[str], title: str) -> TableWriter:
    table = TableWriter(table_name=title, headers=headerkeys, value_matrix=[])
    return table

def print_rich_table(
    table: Union[Table, TableWriter]
) -> None

Prints a rich.Table view.

Source code in hpcman/hpcman/software/db.py
def print_rich_table(table: Union[Table, TableWriter]) -> None:
    """
    Prints a rich.Table view.
    """
    console = Console()
    try:
        print(table.dumps())
    except AttributeError:
        console.print(table)

show_info

def show_info(
    name: str,
    db_path: Path
) -> None
Source code in hpcman/hpcman/software/db.py
def show_info(name: str, db_path: Path) -> None:
    engine = start_engine(db_path=db_path)

    with Session(engine) as session:
        software = session.exec(select(Software).where(Software.name == name)).one_or_none()
        if software is None:
            rprint(f"There is no software named '{name}'.")
            software_names = session.exec(select(Software.name)).all()
            rprint(f"Choose one from this list: {software_names}")
            exit(1)
        pprint(software.model_dump(mode="json"))
        for installed_version in software.versions:
            pprint(installed_version.model_dump(mode="json"))