Skip to content

Impure

Bases: PurityResult

Class for impure results.

A function is impure if it has at least one (File-, NonLocalVariable-)Read OR (File-, NonLocalVariable-)Write side effect. An impure function must also have no unknown reasons.

Be aware that a function can be impure because of multiple reasons. Also, Impure != Pure since: not Pure would mean a function is either unknown or has at least one (File-, NonLocalVariable-)Read OR (File-, NonLocalVariable-)Write side effect.

Attributes:

Name Type Description
reasons set[ImpurityReason]

The reasons why the function is impure.

is_class bool

Whether the result is for a class or not.

Source code in src/library_analyzer/processing/api/purity_analysis/model/_purity.py
@dataclass
class Impure(PurityResult):
    """Class for impure results.

    A function is impure if it has at least one
    (File-, NonLocalVariable-)Read OR (File-, NonLocalVariable-)Write side effect.
    An impure function must also have no unknown reasons.

    Be aware that a function can be impure because of multiple reasons.
    Also, Impure != Pure since: not Pure would mean a function is either unknown or has at least one
    (File-, NonLocalVariable-)Read OR (File-, NonLocalVariable-)Write side effect.

    Attributes
    ----------
    reasons :
        The reasons why the function is impure.
    is_class :
        Whether the result is for a class or not.
    """

    reasons: set[ImpurityReason]
    is_class: bool = False

    def update(self, other: PurityResult | None) -> PurityResult:
        """Update the current result with another result.

        Parameters
        ----------
        other :
            The result to update with.

        Returns
        -------
        PurityResult
            The updated result.

        Raises
        ------
        TypeError
            If the result cannot be updated with the given result.
        """
        if other is None:
            return self.clone()
        elif isinstance(self, Pure):
            if isinstance(other, Pure):
                return self.clone()
            elif isinstance(other, Impure):
                return other.clone()
        elif isinstance(self, Impure):
            if isinstance(other, Pure):
                return self.clone()
            elif isinstance(other, Impure):
                return Impure(reasons=self.reasons | other.reasons).clone()
        raise TypeError(f"Cannot update {self} with {other}")

    def clone(self) -> Impure:
        return Impure(reasons=self.reasons.copy())

    def to_dict(self, shorten: bool = False) -> dict[str, Any]:
        seen = set()
        non_local_variable_reads = []
        non_local_variable_writes = []
        file_reads = []
        file_writes = []
        unknown_calls = []
        native_calls = []
        parameter_calls = []
        for reason in self.reasons:
            if str(reason) not in seen:
                seen.add(str(reason))
                match reason:
                    case NonLocalVariableRead():
                        non_local_variable_reads.append(reason.to_dict())
                    case NonLocalVariableWrite():
                        non_local_variable_writes.append(reason.to_dict())
                    case FileRead():
                        file_reads.append(reason.to_dict())
                    case FileWrite():
                        file_writes.append(reason.to_dict())
                    case UnknownCall():
                        unknown_calls.append(reason.to_dict())
                    case NativeCall():
                        native_calls.append(reason.to_dict())
                    case CallOfParameter():
                        parameter_calls.append(reason.to_dict())
                    case _:
                        raise TypeError(f"Unknown reason type: {reason}")
        if not shorten:
            combined_reasons: dict[str, Any] = {
                "NonLocalVariableRead": non_local_variable_reads,
                "NonLocalVariableWrite": non_local_variable_writes,
                "FileRead": file_reads,
                "FileWrite": file_writes,
                "UnknownCall": unknown_calls,
                "NativeCall": native_calls,
                "CallOfParameter": parameter_calls,
            }
        else:
            combined_reasons = {
                "NonLocalVariableRead": len(non_local_variable_reads),
                "NonLocalVariableWrite": len(non_local_variable_writes),
                "FileRead": len(file_reads),
                "FileWrite": len(file_writes),
                "UnknownCall": len(unknown_calls),
                "NativeCall": len(native_calls),
                "CallOfParameter": len(parameter_calls),
            }
        return {
            "purity": self.__class__.__name__,
            "reasons": {reason: value for reason, value in combined_reasons.items() if value},
        }

    def __hash__(self) -> int:
        return hash(str(self))

    def __str__(self) -> str:
        return f"{self.__class__.__name__}"

is_class: bool = False class-attribute instance-attribute

reasons: set[ImpurityReason] instance-attribute

__hash__()

Source code in src/library_analyzer/processing/api/purity_analysis/model/_purity.py
def __hash__(self) -> int:
    return hash(str(self))

__init__(reasons, is_class=False)

__str__()

Source code in src/library_analyzer/processing/api/purity_analysis/model/_purity.py
def __str__(self) -> str:
    return f"{self.__class__.__name__}"

clone()

Source code in src/library_analyzer/processing/api/purity_analysis/model/_purity.py
def clone(self) -> Impure:
    return Impure(reasons=self.reasons.copy())

to_dict(shorten=False)

Source code in src/library_analyzer/processing/api/purity_analysis/model/_purity.py
def to_dict(self, shorten: bool = False) -> dict[str, Any]:
    seen = set()
    non_local_variable_reads = []
    non_local_variable_writes = []
    file_reads = []
    file_writes = []
    unknown_calls = []
    native_calls = []
    parameter_calls = []
    for reason in self.reasons:
        if str(reason) not in seen:
            seen.add(str(reason))
            match reason:
                case NonLocalVariableRead():
                    non_local_variable_reads.append(reason.to_dict())
                case NonLocalVariableWrite():
                    non_local_variable_writes.append(reason.to_dict())
                case FileRead():
                    file_reads.append(reason.to_dict())
                case FileWrite():
                    file_writes.append(reason.to_dict())
                case UnknownCall():
                    unknown_calls.append(reason.to_dict())
                case NativeCall():
                    native_calls.append(reason.to_dict())
                case CallOfParameter():
                    parameter_calls.append(reason.to_dict())
                case _:
                    raise TypeError(f"Unknown reason type: {reason}")
    if not shorten:
        combined_reasons: dict[str, Any] = {
            "NonLocalVariableRead": non_local_variable_reads,
            "NonLocalVariableWrite": non_local_variable_writes,
            "FileRead": file_reads,
            "FileWrite": file_writes,
            "UnknownCall": unknown_calls,
            "NativeCall": native_calls,
            "CallOfParameter": parameter_calls,
        }
    else:
        combined_reasons = {
            "NonLocalVariableRead": len(non_local_variable_reads),
            "NonLocalVariableWrite": len(non_local_variable_writes),
            "FileRead": len(file_reads),
            "FileWrite": len(file_writes),
            "UnknownCall": len(unknown_calls),
            "NativeCall": len(native_calls),
            "CallOfParameter": len(parameter_calls),
        }
    return {
        "purity": self.__class__.__name__,
        "reasons": {reason: value for reason, value in combined_reasons.items() if value},
    }

update(other)

Update the current result with another result.

Parameters:

Name Type Description Default
other PurityResult | None

The result to update with.

required

Returns:

Type Description
PurityResult

The updated result.

Raises:

Type Description
TypeError

If the result cannot be updated with the given result.

Source code in src/library_analyzer/processing/api/purity_analysis/model/_purity.py
def update(self, other: PurityResult | None) -> PurityResult:
    """Update the current result with another result.

    Parameters
    ----------
    other :
        The result to update with.

    Returns
    -------
    PurityResult
        The updated result.

    Raises
    ------
    TypeError
        If the result cannot be updated with the given result.
    """
    if other is None:
        return self.clone()
    elif isinstance(self, Pure):
        if isinstance(other, Pure):
            return self.clone()
        elif isinstance(other, Impure):
            return other.clone()
    elif isinstance(self, Impure):
        if isinstance(other, Pure):
            return self.clone()
        elif isinstance(other, Impure):
            return Impure(reasons=self.reasons | other.reasons).clone()
    raise TypeError(f"Cannot update {self} with {other}")