Skip to content

Exceptions

See Handling errors for the guide.

thresher.exceptions

The exceptions this package raises, and the wording they carry.

Everything raised from thresher derives from ThresherError, so a caller can catch this package's failures without also catching unrelated ones:

try:
    Thresher().optimize_threshold(scores, actual_classes)
except thresher.exceptions.InvalidInputError as exc:
    ...

Each class also inherits the builtin it used to be raised as - ValueError, TypeError, AttributeError, ImportError, NotImplementedError. That is deliberate rather than decorative: code written against any earlier version catches those builtins, and this package's own command line catches ValueError and ImportError. Dual inheritance makes the hierarchy an addition rather than a breaking change.

Where an error carries useful detail - how many scores, which labels, what was available - it is kept on the instance as well as formatted into the message, so callers can act on it instead of parsing prose.

The message templates stay as module constants. They define the wording, tests assert against the same strings the user sees, and they were importable before the classes existed.

ThresherError

Bases: Exception

Base class for every error raised by this package.

Catch this to handle anything thresher rejects, without also catching failures from numpy, pandas or your own code that happen to use the same builtin types.

ConfigurationError

Bases: ThresherError, ValueError

Something was asked for that does not exist - a mistyped name, usually.

Raised while an object is being built, before any data is touched.

UnknownAlgorithmError

UnknownAlgorithmError(name: Any, available: Iterable[str])

Bases: ConfigurationError

No algorithm goes by that name, or any of its synonyms.

Record what was asked for and what would have worked.

Parameters:

Name Type Description Default
name Any

the name that matched nothing. Usually a mistyped string, but anything arrives here - a non-string is as unknown as a wrong spelling.

required
available Iterable[str]

the algorithm ids that would have.

required
Source code in src/thresher/exceptions.py
def __init__(self, name: Any, available: Iterable[str]) -> None:
    """Record what was asked for and what would have worked.

    Args:
        name: the name that matched nothing. Usually a mistyped string, but anything
            arrives here - a non-string is as unknown as a wrong spelling.
        available: the algorithm ids that would have.
    """
    self.name = name
    self.available = sorted(available)
    super().__init__(UNKNOWN_ALGORITHM_NAME.format(name=name, available=", ".join(self.available)))

UnknownBackendError

UnknownBackendError(name: Any, available: Iterable[str])

Bases: ConfigurationError

No execution backend goes by that name.

Record what was asked for and what would have worked.

Parameters:

Name Type Description Default
name Any

the name that matched nothing.

required
available Iterable[str]

the backend names that would have.

required
Source code in src/thresher/exceptions.py
def __init__(self, name: Any, available: Iterable[str]) -> None:
    """Record what was asked for and what would have worked.

    Args:
        name: the name that matched nothing.
        available: the backend names that would have.
    """
    self.name = name
    self.available = list(available)
    super().__init__(UNKNOWN_BACKEND_NAME.format(name=name, available=", ".join(self.available)))

InvalidInputError

Bases: ThresherError, ValueError

The data cannot be optimized over as given.

EmptyInputError

EmptyInputError()

Bases: InvalidInputError

There is nothing to optimize.

Source code in src/thresher/exceptions.py
def __init__(self) -> None:
    super().__init__(EMPTY_INPUT)

LengthMismatchError

LengthMismatchError(score_count: int, class_count: int)

Bases: InvalidInputError

The scores and the classes do not line up one to one.

Record both counts, so a caller can report or repair the difference.

Parameters:

Name Type Description Default
score_count int

how many scores were given.

required
class_count int

how many classes were given.

required
Source code in src/thresher/exceptions.py
def __init__(self, score_count: int, class_count: int) -> None:
    """Record both counts, so a caller can report or repair the difference.

    Args:
        score_count: how many scores were given.
        class_count: how many classes were given.
    """
    self.score_count = score_count
    self.class_count = class_count
    super().__init__(LENGTH_MISMATCH.format(scores=score_count, classes=class_count))

MissingLabelsError

MissingLabelsError(count: int)

Bases: InvalidInputError

Some scores have no class at all - a blank cell arrives as NaN.

Record how many are missing.

Parameters:

Name Type Description Default
count int

number of missing values found.

required
Source code in src/thresher/exceptions.py
def __init__(self, count: int) -> None:
    """Record how many are missing.

    Args:
        count: number of missing values found.
    """
    self.count = count
    super().__init__(MISSING_LABELS.format(count=count))

UndefinedScoresError

UndefinedScoresError(count: int)

Bases: InvalidInputError

Some scores are NaN, so no threshold can be placed relative to them.

Distinct from MissingLabelsError, which is the same problem on the other column. Before 0.7.1 this went unchecked and each algorithm failed its own way: exact returned NaN as though it were an answer - a threshold that classifies everything negative, since every comparison against NaN is false - while hist raised a bare ValueError from its bin arithmetic.

Record how many are undefined.

Parameters:

Name Type Description Default
count int

number of NaN scores found.

required
Source code in src/thresher/exceptions.py
def __init__(self, count: int) -> None:
    """Record how many are undefined.

    Args:
        count: number of NaN scores found.
    """
    self.count = count
    super().__init__(UNDEFINED_SCORES.format(count=count))

UnexpectedLabelsError

UnexpectedLabelsError(unexpected: Iterable[Any])

Bases: InvalidInputError

Labels outside the -1 / 1 pair the solvers work in.

Record the offending values.

Parameters:

Name Type Description Default
unexpected Iterable[Any]

the label values that are neither -1 nor 1.

required
Source code in src/thresher/exceptions.py
def __init__(self, unexpected: Iterable[Any]) -> None:
    """Record the offending values.

    Args:
        unexpected: the label values that are neither -1 nor 1.
    """
    self.unexpected = list(unexpected)
    formatted = ", ".join(repr(value) for value in self.unexpected)
    super().__init__(UNEXPECTED_LABELS.format(unexpected=formatted))

SingleClassError

SingleClassError(only: Any)

Bases: InvalidInputError

Only one of the two classes is present, so there is nothing to separate.

Record the class that was found.

Parameters:

Name Type Description Default
only Any

the single label value present.

required
Source code in src/thresher/exceptions.py
def __init__(self, only: Any) -> None:
    """Record the class that was found.

    Args:
        only: the single label value present.
    """
    self.only = only
    super().__init__(SINGLE_CLASS_LABELS.format(only=repr(only)))

InsufficientDataError

Bases: InvalidInputError

Too little data for this algorithm to produce a candidate threshold.

LabelMappingError

Bases: ThresherError, TypeError

The labels option cannot map the classes it was given.

NotIterableError

NotIterableError(attribute: str = 'scores')

Bases: ThresherError, AttributeError

scores or actual_classes is not something that can be iterated.

Inherits AttributeError because that is what earlier versions raised. TypeError would fit the failure better, but changing it would break existing except clauses for no practical gain.

Record which argument was not iterable, and name it in the message.

Parameters:

Name Type Description Default
attribute str

the offending argument - "scores" or "actual_classes". The default keeps the historical wording, which only ever blamed the former.

'scores'
Source code in src/thresher/exceptions.py
def __init__(self, attribute: str = "scores") -> None:
    """Record which argument was not iterable, and name it in the message.

    Args:
        attribute: the offending argument - `"scores"` or `"actual_classes"`. The
            default keeps the historical wording, which only ever blamed the former.
    """
    self.attribute = attribute
    super().__init__(NOT_ITERABLE.format(attribute=attribute))

BackendDependencyError

Bases: ThresherError, ImportError

A backend was selected whose optional dependency is not installed.

ParallelBootstrapError

Bases: ThresherError, RuntimeError

Worker processes could not be started, so the work never ran.

Inherits RuntimeError because that is what BrokenProcessPool - the failure this replaces - already was, so an except RuntimeError written around a parallel run keeps working. Before 0.7.0 this situation had no exception at all: multiprocessing.Pool waited on workers that would never report, and the process simply hung.

AlgorithmNotWiredError

AlgorithmNotWiredError(message: str = UNKNOWN_ALGORITHM)

Bases: ThresherError, NotImplementedError

An algorithm is in the registry but has no branch in the dispatcher.

A mistake in the package rather than in the caller's code: it means available_algorithms and run_computations have drifted apart.

Source code in src/thresher/exceptions.py
def __init__(self, message: str = UNKNOWN_ALGORITHM) -> None:
    super().__init__(message)

ShardMergeError

Bases: ThresherError, ValueError

Partial results from a distributed run could not be combined.

Also a package-level mistake rather than a caller's: the shards disagree about how many candidates were scored, which cannot happen within a single run.