from __future__ import annotations from typing import TypeGuard __all__ = [ "InvalidNumbersError", "InvalidNumbersContainerError", "InvalidNumbersItemError", "sum_of_squares_of_even_numbers", ] class InvalidNumbersError(TypeError): """Base class for validation errors raised by this module.""" class InvalidNumbersContainerError(InvalidNumbersError): """Raised when the top-level ``numbers`` argument is invalid.""" def __init__(self, value: object) -> None: self.value = value if value is None: message = "numbers cannot be None" else: message = ( f"numbers must be a list of built-in integers, " f"got {type(value).__name__}" ) super().__init__(message) class InvalidNumbersItemError(InvalidNumbersError): """Raised when an element in ``numbers`` is invalid.""" def __init__(self, *, index: int, value: object) -> None: self.index = index self.value = value super().__init__( f"numbers[{index}] must be a built-in int, " f"got {type(value).__name__}: {value!r}" ) def sum_of_squares_of_even_numbers(numbers: list[int]) -> int: """Return the sum of squared even integers in ``numbers``. This function intentionally accepts only ``list[int]`` and only built-in ``int`` elements. That keeps the contract explicit and avoids ambiguity around ``bool``, integer subclasses, and third-party numeric types. Args: numbers: A list of built-in integers. Returns: The sum of ``n * n`` for each even integer in ``numbers``. Raises: InvalidNumbersContainerError: If ``numbers`` is not a list. InvalidNumbersItemError: If any element is not a built-in ``int``. Examples: >>> sum_of_squares_of_even_numbers([1, 2, 3, 4]) 20 >>> sum_of_squares_of_even_numbers([]) 0 """ if numbers is None or not isinstance(numbers, list): raise InvalidNumbersContainerError(numbers) total = 0 for index, value in enumerate(numbers): if not _is_builtin_int(value): raise InvalidNumbersItemError(index=index, value=value) if value % 2 == 0: total += value * value return total def _is_builtin_int(value: object) -> TypeGuard[int]: """Return ``True`` only for built-in ``int`` values.""" return type(value) is int # Tests import pytest def test_sum_of_squares_of_even_numbers_happy_path(): assert sum_of_squares_of_even_numbers([1, 2, 3, 4, 6]) == 56 def test_sum_of_squares_of_even_numbers_empty_list(): assert sum_of_squares_of_even_numbers([]) == 0 def test_sum_of_squares_of_even_numbers_rejects_non_list_input(): with pytest.raises(InvalidNumbersContainerError) as exc_info: sum_of_squares_of_even_numbers((2, 4, 6)) # type: ignore[arg-type] assert exc_info.value.value == (2, 4, 6) def test_sum_of_squares_of_even_numbers_rejects_invalid_item(): with pytest.raises(InvalidNumbersItemError) as exc_info: sum_of_squares_of_even_numbers([2, 3.5, 4]) # type: ignore[list-item] assert exc_info.value.index == 1 assert exc_info.value.value == 3.5 def test_sum_of_squares_of_even_numbers_rejects_bool(): with pytest.raises(InvalidNumbersItemError) as exc_info: sum_of_squares_of_even_numbers([2, True, 4]) assert exc_info.value.index == 1 assert exc_info.value.value is True