Coverage for src / jquantstats / exceptions.py: 100%
28 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-26 18:44 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-26 18:44 +0000
1"""Domain-specific exception types for the jquantstats package.
3This module defines a hierarchy of exceptions that provide meaningful context
4when data-validation errors occur within the package.
6All exceptions inherit from :class:`JQuantStatsError` so callers can catch the
7entire family with a single ``except JQuantStatsError`` clause if they prefer.
9Examples:
10 >>> raise MissingDateColumnError("prices") # doctest: +ELLIPSIS
11 Traceback (most recent call last):
12 ...
13 jquantstats.exceptions.MissingDateColumnError: ...
14"""
16from __future__ import annotations
19class JQuantStatsError(Exception):
20 """Base class for all JQuantStats domain errors."""
23class MissingDateColumnError(JQuantStatsError, ValueError):
24 """Raised when a required ``'date'`` column is absent from a DataFrame.
26 Args:
27 frame_name: Descriptive name of the frame missing the column (e.g. ``"prices"``).
29 Examples:
30 >>> raise MissingDateColumnError("prices") # doctest: +ELLIPSIS
31 Traceback (most recent call last):
32 ...
33 jquantstats.exceptions.MissingDateColumnError: ...
34 """
36 def __init__(self, frame_name: str) -> None:
37 """Initialize with the name of the frame that is missing the column."""
38 super().__init__(f"DataFrame '{frame_name}' is missing the required 'date' column.")
39 self.frame_name = frame_name
42class InvalidCashPositionTypeError(JQuantStatsError, TypeError):
43 """Raised when ``cashposition`` is not a :class:`polars.DataFrame`.
45 Args:
46 actual_type: The ``type.__name__`` of the value that was supplied.
48 Examples:
49 >>> raise InvalidCashPositionTypeError("dict")
50 Traceback (most recent call last):
51 ...
52 jquantstats.exceptions.InvalidCashPositionTypeError: cashposition must be pl.DataFrame, got dict.
53 """
55 def __init__(self, actual_type: str) -> None:
56 """Initialize with the offending type name."""
57 super().__init__(f"cashposition must be pl.DataFrame, got {actual_type}.")
58 self.actual_type = actual_type
61class InvalidPricesTypeError(JQuantStatsError, TypeError):
62 """Raised when ``prices`` is not a :class:`polars.DataFrame`.
64 Args:
65 actual_type: The ``type.__name__`` of the value that was supplied.
67 Examples:
68 >>> raise InvalidPricesTypeError("list")
69 Traceback (most recent call last):
70 ...
71 jquantstats.exceptions.InvalidPricesTypeError: prices must be pl.DataFrame, got list.
72 """
74 def __init__(self, actual_type: str) -> None:
75 """Initialize with the offending type name."""
76 super().__init__(f"prices must be pl.DataFrame, got {actual_type}.")
77 self.actual_type = actual_type
80class NonPositiveAumError(JQuantStatsError, ValueError):
81 """Raised when ``aum`` is not strictly positive.
83 Args:
84 aum: The non-positive value that was supplied.
86 Examples:
87 >>> raise NonPositiveAumError(0.0)
88 Traceback (most recent call last):
89 ...
90 jquantstats.exceptions.NonPositiveAumError: aum must be strictly positive, got 0.0.
91 """
93 def __init__(self, aum: float) -> None:
94 """Initialize with the offending aum value."""
95 super().__init__(f"aum must be strictly positive, got {aum}.")
96 self.aum = aum
99class RowCountMismatchError(JQuantStatsError, ValueError):
100 """Raised when ``prices`` and ``cashposition`` have different numbers of rows.
102 Args:
103 prices_rows: Number of rows in the prices DataFrame.
104 cashposition_rows: Number of rows in the cashposition DataFrame.
106 Examples:
107 >>> raise RowCountMismatchError(10, 9) # doctest: +ELLIPSIS
108 Traceback (most recent call last):
109 ...
110 jquantstats.exceptions.RowCountMismatchError: ...
111 """
113 def __init__(self, prices_rows: int, cashposition_rows: int) -> None:
114 """Initialize with the row counts of the two mismatched DataFrames."""
115 super().__init__(
116 f"cashposition and prices must have the same number of rows, "
117 f"got cashposition={cashposition_rows} and prices={prices_rows}."
118 )
119 self.prices_rows = prices_rows
120 self.cashposition_rows = cashposition_rows
123class IntegerIndexBoundError(JQuantStatsError, TypeError):
124 """Raised when a row-index bound is not an integer.
126 Args:
127 param: Name of the offending parameter (e.g. ``"start"`` or ``"end"``).
128 actual_type: The ``type.__name__`` of the value that was supplied.
130 Examples:
131 >>> raise IntegerIndexBoundError("start", "str")
132 Traceback (most recent call last):
133 ...
134 jquantstats.exceptions.IntegerIndexBoundError: start must be an integer, got str.
135 """
137 def __init__(self, param: str, actual_type: str) -> None:
138 """Initialize with the parameter name and the offending type."""
139 super().__init__(f"{param} must be an integer, got {actual_type}.")
140 self.param = param
141 self.actual_type = actual_type