Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 13 additions & 18 deletions src/nitypes/_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import numpy as np
import numpy.typing as npt

from nitypes._exceptions import invalid_arg_type, invalid_arg_value, unsupported_arg


def arg_to_float(
arg_description: str, value: SupportsFloat | None, default_value: float | None = None
Expand Down Expand Up @@ -39,21 +41,15 @@ def arg_to_float(
"""
if value is None:
if default_value is None:
raise TypeError(
f"The {arg_description} must be a floating point number.\n\n"
f"Provided value: {value!r}"
)
raise invalid_arg_type(arg_description, "floating point number", value)
value = default_value

if not isinstance(value, float):
try:
# Use value.__float__() because float(value) also accepts strings.
return value.__float__()
except Exception:
raise TypeError(
f"The {arg_description} must be a floating point number.\n\n"
f"Provided value: {value!r}"
) from None
raise invalid_arg_type(arg_description, "floating point number", value) from None

return value

Expand Down Expand Up @@ -90,18 +86,14 @@ def arg_to_int(
"""
if value is None:
if default_value is None:
raise TypeError(
f"The {arg_description} must be an integer.\n\n" f"Provided value: {value!r}"
)
raise invalid_arg_type(arg_description, "integer", value)
value = default_value

if not isinstance(value, int):
try:
return operator.index(value)
except Exception:
raise TypeError(
f"The {arg_description} must be an integer.\n\n" f"Provided value: {value!r}"
) from None
raise invalid_arg_type(arg_description, "integer", value) from None

return value

Expand Down Expand Up @@ -132,10 +124,7 @@ def arg_to_uint(
"""
value = arg_to_int(arg_description, value, default_value)
if value < 0:
raise ValueError(
f"The {arg_description} must be a non-negative integer.\n\n"
f"Provided value: {value!r}"
)
raise invalid_arg_value(arg_description, "non-negative integer", value)
return value


Expand Down Expand Up @@ -163,3 +152,9 @@ def validate_dtype(dtype: npt.DTypeLike, supported_dtypes: tuple[npt.DTypeLike,
f"Data type: {np.dtype(dtype)}\n"
f"Supported data types: {', '.join(supported_dtype_names)}"
)


def validate_unsupported_arg(arg_description: str, value: object) -> None:
"""Validate that an unsupported argument is None."""
if value is not None:
raise unsupported_arg(arg_description, value)
38 changes: 38 additions & 0 deletions src/nitypes/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,41 @@ def add_note(exception: Exception, note: str) -> None:
else:
message = exception.args[0] + "\n" + note
exception.args = (message,) + exception.args[1:]


def invalid_arg_value(
arg_description: str, valid_value_description: str, value: object
) -> ValueError:
"""Create a ValueError for an invalid argument value."""
return ValueError(
f"The {arg_description} must be {_a(valid_value_description)}.\n\n"
f"Provided value: {value!r}"
)


def invalid_arg_type(arg_description: str, type_description: str, value: object) -> TypeError:
"""Create a TypeError for an invalid argument type."""
return TypeError(
f"The {arg_description} must be {_a(type_description)}.\n\n" f"Provided value: {value!r}"
)


def invalid_requested_type(type_description: str, requested_type: type) -> TypeError:
"""Create a TypeError for an invalid requested type."""
raise TypeError(
f"The requested type must be {_a(type_description)} type.\n\n"
f"Requested type: {requested_type}"
)


def unsupported_arg(arg_description: str, value: object) -> ValueError:
"""Create a ValueError for an unsupported argument."""
raise ValueError(
f"The {arg_description} argument is not supported.\n\n" f"Provided value: {value!r}"
)


# English-specific hack. This is why we prefer "Key: value" for localizable errors.
def _a(noun: str) -> str:
indefinite_article = "an" if noun[0] in "AEIOUaeiou" else "a"
return f"{indefinite_article} {noun}"
18 changes: 8 additions & 10 deletions src/nitypes/time/_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import hightime as ht

from nitypes._exceptions import invalid_arg_type, invalid_requested_type

if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
Expand All @@ -24,15 +26,13 @@ def convert_datetime(requested_type: type[_TDateTime], value: _AnyDateTime, /) -
"""Convert a datetime object to the specified type."""
convert_func = _CONVERT_DATETIME_FOR_TYPE.get(requested_type)
if convert_func is None:
raise TypeError(
"The requested type must be a datetime type.\n\n" f"Requested type: {requested_type}"
)
raise invalid_requested_type("datetime", requested_type)
return cast(_TDateTime, convert_func(value))


@singledispatch
def _convert_to_dt_datetime(value: object, /) -> dt.datetime:
raise TypeError("The value must be a datetime.\n\n" f"Provided value: {value!r}")
raise invalid_arg_type("value", "datetime", value)


@_convert_to_dt_datetime.register
Expand All @@ -57,7 +57,7 @@ def _(value: ht.datetime, /) -> dt.datetime:

@singledispatch
def _convert_to_ht_datetime(value: object, /) -> ht.datetime:
raise TypeError("The value must be a datetime.\n\n" f"Provided value: {value!r}")
raise invalid_arg_type("value", "datetime", value)


@_convert_to_ht_datetime.register
Expand Down Expand Up @@ -90,15 +90,13 @@ def convert_timedelta(requested_type: type[_TTimeDelta], value: _AnyTimeDelta, /
"""Convert a timedelta object to the specified type."""
convert_func = _CONVERT_TIMEDELTA_FOR_TYPE.get(requested_type)
if convert_func is None:
raise TypeError(
"The requested type must be a timedelta type.\n\n" f"Requested type: {requested_type}"
)
raise invalid_requested_type("timedelta", requested_type)
return cast(_TTimeDelta, convert_func(value))


@singledispatch
def _convert_to_dt_timedelta(value: object, /) -> dt.timedelta:
raise TypeError("The value must be a timedelta.\n\n" f"Provided value: {value!r}")
raise invalid_arg_type("value", "timedelta", value)


@_convert_to_dt_timedelta.register
Expand All @@ -113,7 +111,7 @@ def _(value: ht.timedelta, /) -> dt.timedelta:

@singledispatch
def _convert_to_ht_timedelta(value: object, /) -> ht.timedelta:
raise TypeError("The value must be a timedelta.\n\n" f"Provided value: {value!r}")
raise invalid_arg_type("value", "timedelta", value)


@_convert_to_ht_timedelta.register
Expand Down
30 changes: 15 additions & 15 deletions src/nitypes/waveform/_analog_waveform.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import numpy.typing as npt

from nitypes._arguments import arg_to_uint, validate_dtype
from nitypes._exceptions import invalid_arg_type
from nitypes.waveform._extended_properties import (
CHANNEL_NAME,
UNIT_DESCRIPTION,
Expand Down Expand Up @@ -125,7 +126,8 @@ def from_array_1d(
if isinstance(array, np.ndarray):
if array.ndim != 1:
raise ValueError(
f"The input array must be a one-dimensional array or sequence.\n\nNumber of dimensions: {array.ndim}"
"The input array must be a one-dimensional array or sequence.\n\n"
f"Number of dimensions: {array.ndim}"
)
elif isinstance(array, Sequence) or (
sys.version_info < (3, 10) and isinstance(array, std_array.array)
Expand All @@ -134,7 +136,8 @@ def from_array_1d(
raise ValueError("You must specify a dtype when the input array is a sequence.")
else:
raise TypeError(
f"The input array must be a one-dimensional array or sequence.\n\nType: {type(array)}"
"The input array must be a one-dimensional array or sequence.\n\n"
f"Type: {type(array)}"
)

return AnalogWaveform(
Expand Down Expand Up @@ -201,7 +204,8 @@ def from_array_2d(
if isinstance(array, np.ndarray):
if array.ndim != 2:
raise ValueError(
f"The input array must be a two-dimensional array or nested sequence.\n\nNumber of dimensions: {array.ndim}"
"The input array must be a two-dimensional array or nested sequence.\n\n"
f"Number of dimensions: {array.ndim}"
)
elif isinstance(array, Sequence) or (
sys.version_info < (3, 10) and isinstance(array, std_array.array)
Expand All @@ -210,7 +214,8 @@ def from_array_2d(
raise ValueError("You must specify a dtype when the input array is a sequence.")
else:
raise TypeError(
f"The input array must be a two-dimensional array or nested sequence.\n\nType: {type(array)}"
"The input array must be a two-dimensional array or nested sequence.\n\n"
f"Type: {type(array)}"
)

return [
Expand Down Expand Up @@ -530,10 +535,7 @@ def capacity(self) -> int:

@capacity.setter
def capacity(self, value: int) -> None:
if value < 0:
raise ValueError(
"The capacity must be a non-negative integer.\n\n" f"Capacity: {value}"
)
value = arg_to_uint("capacity", value)
if value < self._start_index + self._sample_count:
raise ValueError(
"The capacity must be equal to or greater than the number of samples in the waveform.\n\n"
Expand Down Expand Up @@ -563,7 +565,7 @@ def channel_name(self) -> str:
@channel_name.setter
def channel_name(self, value: str) -> None:
if not isinstance(value, str):
raise TypeError("The channel name must be a str.\n\n" f"Provided value: {value!r}")
raise invalid_arg_type("channel name", "str", value)
self._extended_properties[CHANNEL_NAME] = value

@property
Expand All @@ -576,7 +578,7 @@ def unit_description(self) -> str:
@unit_description.setter
def unit_description(self, value: str) -> None:
if not isinstance(value, str):
raise TypeError("The unit description must be a str.\n\n" f"Provided value: {value!r}")
raise invalid_arg_type("unit description", "str", value)
self._extended_properties[UNIT_DESCRIPTION] = value

@property
Expand All @@ -597,7 +599,7 @@ def timing(self) -> Timing:
@timing.setter
def timing(self, value: Timing) -> None:
if not isinstance(value, Timing):
raise TypeError("The timing information must be a Timing object.")
raise invalid_arg_type("timing information", "Timing object", value)
self._timing = value
self._precision_timing = None

Expand Down Expand Up @@ -634,7 +636,7 @@ def precision_timing(self) -> PrecisionTiming:
@precision_timing.setter
def precision_timing(self, value: PrecisionTiming) -> None:
if not isinstance(value, PrecisionTiming):
raise TypeError("The precision timing information must be a PrecisionTiming object.")
raise invalid_arg_type("precision timing information", "PrecisionTiming object", value)
self._precision_timing = value
self._timing = None

Expand All @@ -646,7 +648,5 @@ def scale_mode(self) -> ScaleMode:
@scale_mode.setter
def scale_mode(self, value: ScaleMode) -> None:
if not isinstance(value, ScaleMode):
raise TypeError(
"The scale mode must be a ScaleMode object.\n\n" f"Provided value: {value!r}"
)
raise invalid_arg_type("scale mode", "ScaleMode object", value)
self._scale_mode = value
50 changes: 14 additions & 36 deletions src/nitypes/waveform/_timing/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from enum import Enum
from typing import Generic, SupportsIndex, TypeVar

from nitypes._exceptions import add_note
from nitypes._arguments import validate_unsupported_arg
from nitypes._exceptions import add_note, invalid_arg_type


class SampleIntervalMode(Enum):
Expand All @@ -28,13 +29,6 @@ class SampleIntervalMode(Enum):
_TTimeDelta_co = TypeVar("_TTimeDelta_co", bound=dt.timedelta)


def _validate_unsupported_arg(arg_description: str, value: object) -> None:
if value is not None:
raise ValueError(
f"The {arg_description} argument is not supported.\n\n" f"Provided value: {value!r}"
)


class BaseTiming(ABC, Generic[_TDateTime_co, _TTimeDelta_co]):
"""Base class for waveform timing information."""

Expand Down Expand Up @@ -118,16 +112,11 @@ def _validate_init_args_none(
datetime_type = self.__class__._get_datetime_type()
timedelta_type = self.__class__._get_timedelta_type()
if not isinstance(timestamp, (datetime_type, type(None))):
raise TypeError(
"The timestamp must be a datetime or None.\n\n" f"Provided value: {timestamp!r}"
)
raise invalid_arg_type("timestamp", "datetime or None", timestamp)
if not isinstance(time_offset, (timedelta_type, type(None))):
raise TypeError(
f"The time offset must be a timedelta or None.\n\n"
f"Provided value: {time_offset!r}"
)
_validate_unsupported_arg("sample interval", sample_interval)
_validate_unsupported_arg("timestamps", timestamps)
raise invalid_arg_type("time offset", "timedelta or None", time_offset)
validate_unsupported_arg("sample interval", sample_interval)
validate_unsupported_arg("timestamps", timestamps)

def _validate_init_args_regular(
self,
Expand All @@ -139,20 +128,12 @@ def _validate_init_args_regular(
datetime_type = self.__class__._get_datetime_type()
timedelta_type = self.__class__._get_timedelta_type()
if not isinstance(timestamp, (datetime_type, type(None))):
raise TypeError(
"The timestamp must be a datetime or None.\n\n" f"Provided value: {timestamp!r}"
)
raise invalid_arg_type("timestamp", "datetime or None", timestamp)
if not isinstance(time_offset, (timedelta_type, type(None))):
raise TypeError(
f"The time offset must be a timedelta or None.\n\n"
f"Provided value: {time_offset!r}"
)
raise invalid_arg_type("time offset", "timedelta or None", time_offset)
if not isinstance(sample_interval, timedelta_type):
raise TypeError(
"The sample interval must be a timedelta.\n\n"
f"Provided value: {sample_interval!r}"
)
_validate_unsupported_arg("timestamps", timestamps)
raise invalid_arg_type("sample interval", "timedelta", sample_interval)
validate_unsupported_arg("timestamps", timestamps)

def _validate_init_args_irregular(
self,
Expand All @@ -162,16 +143,13 @@ def _validate_init_args_irregular(
timestamps: Sequence[_TDateTime_co] | None,
) -> None:
datetime_type = self.__class__._get_datetime_type()
_validate_unsupported_arg("timestamp", timestamp)
_validate_unsupported_arg("time offset", time_offset)
_validate_unsupported_arg("sample interval", sample_interval)
validate_unsupported_arg("timestamp", timestamp)
validate_unsupported_arg("time offset", time_offset)
validate_unsupported_arg("sample interval", sample_interval)
if not isinstance(timestamps, Sequence) or not all(
isinstance(ts, datetime_type) for ts in timestamps
):
raise TypeError(
"The timestamps must be a sequence of datetime objects.\n\n"
f"Provided value: {timestamps!r}"
)
raise invalid_arg_type("timestamps", "sequence of datetime objects", timestamps)

_VALIDATE_INIT_ARGS_FOR_MODE = {
SampleIntervalMode.NONE: _validate_init_args_none,
Expand Down
Loading