Module cli_tool_audit.models

This module contains dataclasses for the tool audit.

Expand source code
"""
This module contains dataclasses for the tool audit.
"""
import datetime
import enum
import hashlib
from dataclasses import asdict, dataclass
from typing import Optional


class SchemaType(enum.Enum):
    SNAPSHOT = "snapshot"
    """Version is entire output of --version switch. Compatibility is by exact match."""
    SEMVER = "semver"
    """Major, minor, patch, pre-release, and build metadata. Compatibility is by specification."""
    PEP440 = "pep440"
    """Superficially looks like a superset of semver. As governed by PEP440."""
    EXISTENCE = "existence"
    """Only check if the tool exists. No version check. If it exists, it is compatible."""

    def __str__(self):
        return self.value


@dataclass
class CliToolConfig:
    """
    Represents what tool and what version the user wants to audit on their system.
    """

    name: str
    """Tool name without path"""
    version: Optional[str] = None
    """Desired version"""
    version_switch: Optional[str] = None
    """Command line switch to get version, e.g. -V, --version, etc."""
    schema: Optional[SchemaType] = None
    """Snapshot, semver, pep440, existence"""
    if_os: Optional[str] = None
    """Which OS this tool is needed for. Single value. Comparison evaluated by prefix. Same values as sys.platform"""
    tags: Optional[list[str]] = None
    install_command: Optional[str] = None
    """Having failed to find the right version what command can be run. Let the user run it."""
    install_docs: Optional[str] = None
    """For failed tool checks, where can the user find out how to install."""

    def cache_hash(self) -> str:
        """
        Generate a hash for a CliToolConfig instance.

        Returns:
            str: The hash of the tool configuration.
        """
        config_str = ""
        for key, value in asdict(self).items():
            config_str += f"{key}={value};"  # Concatenate key-value pairs

        # Use hashlib to compute an MD5 hash of the concatenated string
        return hashlib.md5(config_str.encode()).hexdigest()  # nosec


@dataclass
class ToolCheckResult:
    """
    Represents the result of a tool check and version check.
    """

    tool: str
    desired_version: str
    is_needed_for_os: bool
    is_available: bool
    is_snapshot: bool
    """Snapshot schema"""
    found_version: Optional[str]
    """Same as snapshot_version, the exact text produced by --version switch."""
    parsed_version: Optional[str]
    """Clean stringification of the version object for current schema."""
    is_compatible: str
    is_broken: bool
    last_modified: Optional[datetime.datetime]
    tool_config: CliToolConfig

    def status(self) -> str:
        """Compress many status fields into one for display
        Returns:
            str: The status of the tool.
        """
        # Status compression
        # Wrong OS. If wrong OS "Wrong OS"
        # Found/Not. If not found "Not Found"
        # If schema = existence, "Found"
        # Runnable/Not. If not, "Not Runnable"
        # Compatible/Not. If yes, "Compatible"
        # If no "Incompatible"
        if not self.is_needed_for_os:
            status = "Wrong OS"
        elif not self.is_available:
            status = "Not available"
        # need schema!
        elif self.tool_config.schema == "existence":
            status = "Available"
        elif self.is_broken:
            status = "Can't run"
        else:
            status = self.is_compatible
        return status

    def is_problem(self) -> bool:
        """Is this tool's state a problem?
        If it is needed for this OS, if it is incompatible or unavailable, it is a problem.

        Returns:
            bool: True if this tool's state is a problem, False otherwise.
        """
        missing_or_incompatible = self.is_compatible != "Compatible" or not self.is_available
        return self.is_needed_for_os and missing_or_incompatible


@dataclass
class ToolAvailabilityResult:
    """
    Represents only if the tool is available or not.
    """

    is_available: bool
    is_broken: bool
    version: Optional[str]
    """Desired version"""
    last_modified: Optional[datetime.datetime]


if __name__ == "__main__":
    # Example usage
    config = CliToolConfig(name="example_tool", version="1.0.0")
    print(config.cache_hash())

Classes

class CliToolConfig (name: str, version: Optional[str] = None, version_switch: Optional[str] = None, schema: Optional[SchemaType] = None, if_os: Optional[str] = None, tags: Optional[list[str]] = None, install_command: Optional[str] = None, install_docs: Optional[str] = None)

Represents what tool and what version the user wants to audit on their system.

Expand source code
@dataclass
class CliToolConfig:
    """
    Represents what tool and what version the user wants to audit on their system.
    """

    name: str
    """Tool name without path"""
    version: Optional[str] = None
    """Desired version"""
    version_switch: Optional[str] = None
    """Command line switch to get version, e.g. -V, --version, etc."""
    schema: Optional[SchemaType] = None
    """Snapshot, semver, pep440, existence"""
    if_os: Optional[str] = None
    """Which OS this tool is needed for. Single value. Comparison evaluated by prefix. Same values as sys.platform"""
    tags: Optional[list[str]] = None
    install_command: Optional[str] = None
    """Having failed to find the right version what command can be run. Let the user run it."""
    install_docs: Optional[str] = None
    """For failed tool checks, where can the user find out how to install."""

    def cache_hash(self) -> str:
        """
        Generate a hash for a CliToolConfig instance.

        Returns:
            str: The hash of the tool configuration.
        """
        config_str = ""
        for key, value in asdict(self).items():
            config_str += f"{key}={value};"  # Concatenate key-value pairs

        # Use hashlib to compute an MD5 hash of the concatenated string
        return hashlib.md5(config_str.encode()).hexdigest()  # nosec

Class variables

var if_os : Optional[str]

Which OS this tool is needed for. Single value. Comparison evaluated by prefix. Same values as sys.platform

var install_command : Optional[str]

Having failed to find the right version what command can be run. Let the user run it.

var install_docs : Optional[str]

For failed tool checks, where can the user find out how to install.

var name : str

Tool name without path

var schema : Optional[SchemaType]

Snapshot, semver, pep440, existence

var tags : Optional[list[str]]
var version : Optional[str]

Desired version

var version_switch : Optional[str]

Command line switch to get version, e.g. -V, –version, etc.

Methods

def cache_hash(self) ‑> str

Generate a hash for a CliToolConfig instance.

Returns

str
The hash of the tool configuration.
Expand source code
def cache_hash(self) -> str:
    """
    Generate a hash for a CliToolConfig instance.

    Returns:
        str: The hash of the tool configuration.
    """
    config_str = ""
    for key, value in asdict(self).items():
        config_str += f"{key}={value};"  # Concatenate key-value pairs

    # Use hashlib to compute an MD5 hash of the concatenated string
    return hashlib.md5(config_str.encode()).hexdigest()  # nosec
class SchemaType (*args, **kwds)

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Expand source code
class SchemaType(enum.Enum):
    SNAPSHOT = "snapshot"
    """Version is entire output of --version switch. Compatibility is by exact match."""
    SEMVER = "semver"
    """Major, minor, patch, pre-release, and build metadata. Compatibility is by specification."""
    PEP440 = "pep440"
    """Superficially looks like a superset of semver. As governed by PEP440."""
    EXISTENCE = "existence"
    """Only check if the tool exists. No version check. If it exists, it is compatible."""

    def __str__(self):
        return self.value

Ancestors

  • enum.Enum

Class variables

var EXISTENCE

Only check if the tool exists. No version check. If it exists, it is compatible.

var PEP440

Superficially looks like a superset of semver. As governed by PEP440.

var SEMVER

Major, minor, patch, pre-release, and build metadata. Compatibility is by specification.

var SNAPSHOT

Version is entire output of –version switch. Compatibility is by exact match.

class ToolAvailabilityResult (is_available: bool, is_broken: bool, version: Optional[str], last_modified: Optional[datetime.datetime])

Represents only if the tool is available or not.

Expand source code
@dataclass
class ToolAvailabilityResult:
    """
    Represents only if the tool is available or not.
    """

    is_available: bool
    is_broken: bool
    version: Optional[str]
    """Desired version"""
    last_modified: Optional[datetime.datetime]

Class variables

var is_available : bool
var is_broken : bool
var last_modified : Optional[datetime.datetime]
var version : Optional[str]

Desired version

class ToolCheckResult (tool: str, desired_version: str, is_needed_for_os: bool, is_available: bool, is_snapshot: bool, found_version: Optional[str], parsed_version: Optional[str], is_compatible: str, is_broken: bool, last_modified: Optional[datetime.datetime], tool_config: CliToolConfig)

Represents the result of a tool check and version check.

Expand source code
@dataclass
class ToolCheckResult:
    """
    Represents the result of a tool check and version check.
    """

    tool: str
    desired_version: str
    is_needed_for_os: bool
    is_available: bool
    is_snapshot: bool
    """Snapshot schema"""
    found_version: Optional[str]
    """Same as snapshot_version, the exact text produced by --version switch."""
    parsed_version: Optional[str]
    """Clean stringification of the version object for current schema."""
    is_compatible: str
    is_broken: bool
    last_modified: Optional[datetime.datetime]
    tool_config: CliToolConfig

    def status(self) -> str:
        """Compress many status fields into one for display
        Returns:
            str: The status of the tool.
        """
        # Status compression
        # Wrong OS. If wrong OS "Wrong OS"
        # Found/Not. If not found "Not Found"
        # If schema = existence, "Found"
        # Runnable/Not. If not, "Not Runnable"
        # Compatible/Not. If yes, "Compatible"
        # If no "Incompatible"
        if not self.is_needed_for_os:
            status = "Wrong OS"
        elif not self.is_available:
            status = "Not available"
        # need schema!
        elif self.tool_config.schema == "existence":
            status = "Available"
        elif self.is_broken:
            status = "Can't run"
        else:
            status = self.is_compatible
        return status

    def is_problem(self) -> bool:
        """Is this tool's state a problem?
        If it is needed for this OS, if it is incompatible or unavailable, it is a problem.

        Returns:
            bool: True if this tool's state is a problem, False otherwise.
        """
        missing_or_incompatible = self.is_compatible != "Compatible" or not self.is_available
        return self.is_needed_for_os and missing_or_incompatible

Class variables

var desired_version : str
var found_version : Optional[str]

Same as snapshot_version, the exact text produced by –version switch.

var is_available : bool
var is_broken : bool
var is_compatible : str
var is_needed_for_os : bool
var is_snapshot : bool

Snapshot schema

var last_modified : Optional[datetime.datetime]
var parsed_version : Optional[str]

Clean stringification of the version object for current schema.

var tool : str
var tool_configCliToolConfig

Methods

def is_problem(self) ‑> bool

Is this tool's state a problem? If it is needed for this OS, if it is incompatible or unavailable, it is a problem.

Returns

bool
True if this tool's state is a problem, False otherwise.
Expand source code
def is_problem(self) -> bool:
    """Is this tool's state a problem?
    If it is needed for this OS, if it is incompatible or unavailable, it is a problem.

    Returns:
        bool: True if this tool's state is a problem, False otherwise.
    """
    missing_or_incompatible = self.is_compatible != "Compatible" or not self.is_available
    return self.is_needed_for_os and missing_or_incompatible
def status(self) ‑> str

Compress many status fields into one for display

Returns

str
The status of the tool.
Expand source code
def status(self) -> str:
    """Compress many status fields into one for display
    Returns:
        str: The status of the tool.
    """
    # Status compression
    # Wrong OS. If wrong OS "Wrong OS"
    # Found/Not. If not found "Not Found"
    # If schema = existence, "Found"
    # Runnable/Not. If not, "Not Runnable"
    # Compatible/Not. If yes, "Compatible"
    # If no "Incompatible"
    if not self.is_needed_for_os:
        status = "Wrong OS"
    elif not self.is_available:
        status = "Not available"
    # need schema!
    elif self.tool_config.schema == "existence":
        status = "Available"
    elif self.is_broken:
        status = "Can't run"
    else:
        status = self.is_compatible
    return status