From 234cc6a77df50ac56afc05bb9e44a864016386e7 Mon Sep 17 00:00:00 2001 From: lykmapipo Date: Sun, 21 Jun 2026 02:07:12 +0300 Subject: [PATCH 1/5] feat(http): add collection and mapping utilities to Result This: - Add `__iter__()` and `__len__()` methods to support iteration and length checking - Add a `map()` method to transform `ResultItem` - Add `extend()` method to append items into the underlying result list --- src/gfwapiclient/http/models/response.py | 70 ++++++++++ tests/http/test_response_result_model.py | 164 ++++++++++++++++++++++- 2 files changed, 233 insertions(+), 1 deletion(-) diff --git a/src/gfwapiclient/http/models/response.py b/src/gfwapiclient/http/models/response.py index d9d35ab..d7d90f1 100644 --- a/src/gfwapiclient/http/models/response.py +++ b/src/gfwapiclient/http/models/response.py @@ -4,6 +4,7 @@ Any, Callable, Generic, + Iterable, Iterator, List, Optional, @@ -38,6 +39,7 @@ class ResultItem(BaseModel): _ResultItemT = TypeVar("_ResultItemT", bound=ResultItem) +_ResultItemMappedT = TypeVar("_ResultItemMappedT") class Result(Generic[_ResultItemT]): @@ -186,6 +188,39 @@ def find( return None + def map( + self, + *, + mapper: Callable[[_ResultItemT], _ResultItemMappedT], + ) -> Iterator[_ResultItemMappedT]: + """Transforms each API endpoint result data using a mapping function. + + This method applies `mapper` to every `ResultItem` and returns an `Iterator` + yielding the transformed `_ResultItemMappedT`. + + Args: + mapper (Callable[[_ResultItemT], _ResultItemMappedT]): + A callable that transform a `ResultItem` instance and + returns transformed `_ResultItemMappedT` instance. + + Yields: + _ResultItemMappedT: + Individual transformed `_ResultItemMappedT` from API endpoint result data. + + Returns: + Iterator[_ResultItemMappedT]: + An iterator over transformed API endpoint result data. + + Raises: + TypeError: + If `mapper` is not callable. + """ + if not callable(mapper): + raise TypeError("Expected `mapper` to be callable.") + + for item in self._iter_data(): + yield mapper(item) + def _iter_data(self) -> Iterator[_ResultItemT]: """Iterate lazily over API endpoint result data without copying. @@ -209,5 +244,40 @@ def _iter_data(self) -> Iterator[_ResultItemT]: else: yield self._data + def __iter__(self) -> Iterator[_ResultItemT]: + """Returns an iterator over API endpoint result data. + + Yields: + _ResultItemT: + Individual `ResultItem` contained in API endpoint result data. + + Returns: + Iterator[_ResultItemT]: + An iterator over API endpoint result data. + """ + yield from self._iter_data() + + def __len__(self) -> int: + """Returns total number of items in API endpoint result data. + + Returns: + int: + The total number of items in API endpoint result data. + """ + return len(list(self._iter_data())) + + def extend( + self, + values: Iterable[_ResultItemT], + ) -> None: + """Extends API endpoint result data by appending items from the iterable. + + Args: + values (Iterable[_ResultItemT]): + Iterable of `ResultItem` to append to API endpoint result data. + """ + if isinstance(self._data, list): + self._data.extend(values) + _ResultT = TypeVar("_ResultT", bound=Result[Any]) diff --git a/tests/http/test_response_result_model.py b/tests/http/test_response_result_model.py index e2c2ac3..0e4c224 100644 --- a/tests/http/test_response_result_model.py +++ b/tests/http/test_response_result_model.py @@ -2,7 +2,7 @@ import datetime -from typing import Any, Dict, Final, List, Optional, Type, cast +from typing import Any, Dict, Final, Iterator, List, Optional, Type, cast import pandas as pd import pytest @@ -446,3 +446,165 @@ def test_result_find_invalid_predicate_returns_none( found: Optional[SampleResultItem] = result.find(predicate=invalid_predicate) assert found is None + + +def test_result_map_transforms_all_result_items( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` map transforms correctly all `ResultItem`.""" + data = [ + SampleResultItem(**mock_result_item), + SampleResultItem(**{**mock_result_item, "id": mock_result_item["id"] * 2}), + ] + + result = SampleListResult(data=data) + + def to_ids(item: SampleResultItem) -> str: + return item.id + + mapped_result: Iterator[str] = result.map(mapper=to_ids) + + assert mapped_result is not None + assert isinstance(mapped_result, Iterator) + + mapped_result_list: List[str] = list(mapped_result) + assert len(mapped_result_list) == 2 + assert mapped_result_list[0] == data[0].id + assert mapped_result_list[1] == data[1].id + + +def test_result_map_transforms_single_result_item( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` map transforms correctly a single `ResultItem`.""" + item = SampleResultItem(**mock_result_item) + + result = SampleSingleResult(data=item) + + def to_ids(item: SampleResultItem) -> str: + return item.id + + mapped_result: Iterator[str] = result.map(mapper=to_ids) + + assert mapped_result is not None + assert isinstance(mapped_result, Iterator) + + mapped_result_list: List[str] = list(mapped_result) + assert len(mapped_result_list) == 1 + assert mapped_result_list[0] == item.id + + +@pytest.mark.parametrize( + "invalid_mapper", + ["invalid", 123, object(), True, [], {}], +) +def test_result_map_raises_type_error_for_non_callable_mapper( + mock_result_item: Dict[str, Any], + invalid_mapper: Any, +) -> None: + """Tests that `Result` raises a `TypeError` when `mapper` is not callable.""" + data = [SampleResultItem(**mock_result_item)] + result = SampleListResult(data=data) + + with pytest.raises(TypeError): + list(result.map(mapper=invalid_mapper)) + + +def test_result_supports_iteration( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` support iteration by implementing `__iter__`.""" + input = {**mock_result_item} + data = [SampleResultItem(**input), SampleResultItem(**{**input, "confidence": 4})] + result = SampleListResult(data=data) + + assert list(result) == data + + for item in result: + assert item is not None + assert isinstance(item, SampleResultItem) + + +def test_result_supports_iteration_with_single_result_item( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` support iteration with a single `ResultItem`.""" + input = {**mock_result_item} + item = SampleResultItem(**input) + result = SampleSingleResult(data=item) + + assert list(result) == [item] + + for item in result: + assert item is not None + assert isinstance(item, SampleResultItem) + + +def test_result_supports_len( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` support len by implementing `__len__`.""" + input = {**mock_result_item} + data = [SampleResultItem(**input), SampleResultItem(**{**input, "confidence": 4})] + result = SampleListResult(data=data) + + assert len(result) == 2 + + +def test_result_supports_len_with_single_result_item( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` support len with a single `ResultItem`.""" + input = {**mock_result_item} + data = SampleResultItem(**input) + result = SampleSingleResult(data=data) + + assert len(result) == 1 + + +def test_result_supports_extending( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` support appending items by implementing `extend`.""" + input = {**mock_result_item} + data = [SampleResultItem(**input)] + result = SampleListResult(data=data) + + assert len(result) == 1 + + values = [SampleResultItem(**{**input, "confidence": 4})] + result.extend(values=values) + + assert len(result) == 2 + + +def test_result_supports_extending_from_other_result( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` support appending items from other `Result`.""" + input = {**mock_result_item} + data = [SampleResultItem(**input)] + result = SampleListResult(data=data) + + assert len(result) == 1 + + values = SampleListResult(data=[SampleResultItem(**{**input, "confidence": 4})]) + result.extend(values=values) + + assert len(result) == 2 + + +def test_result_does_not_support_extending_with_single_result_item( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` does not support appending items by implementing `extend`.""" + input = {**mock_result_item} + item = SampleResultItem(**input) + result = SampleSingleResult(item) + + assert len(result) == 1 + + values = [SampleResultItem(**{**input, "confidence": 4})] + result.extend(values=values) + + assert len(result) == 1 From 0de5466bb9af6f114b9ee340c2cd091675497baa Mon Sep 17 00:00:00 2001 From: lykmapipo Date: Sun, 21 Jun 2026 15:13:40 +0300 Subject: [PATCH 2/5] refactor(http): replace Result extend with immutable __add__ method This: - Remove the `.extend()` instance method from the `Result` class - Implement the `__add__` dunder method to allow merging `Result` with `Iterable` or `Result` --- src/gfwapiclient/http/models/response.py | 27 +++++++--- tests/http/test_response_result_model.py | 68 +++++++++++++----------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/gfwapiclient/http/models/response.py b/src/gfwapiclient/http/models/response.py index d7d90f1..1a2824a 100644 --- a/src/gfwapiclient/http/models/response.py +++ b/src/gfwapiclient/http/models/response.py @@ -266,18 +266,29 @@ def __len__(self) -> int: """ return len(list(self._iter_data())) - def extend( - self, - values: Iterable[_ResultItemT], - ) -> None: - """Extends API endpoint result data by appending items from the iterable. + def __add__(self, other: Iterable[_ResultItemT]) -> "Result[_ResultItemT]": + """Concatenates API endpoint result data with items from another iterable. + + This method returns a new `Result` instance containing combined + `ResultItem` objects. Args: - values (Iterable[_ResultItemT]): + other (Iterable[_ResultItemT]): Iterable of `ResultItem` to append to API endpoint result data. + + Returns: + Result[_ResultItemT]: + A new `Result` instance containing the combined `ResultItem` objects. + + Raises: + TypeError: + If `other` is not iterable. """ - if isinstance(self._data, list): - self._data.extend(values) + if not isinstance(other, Iterable): + raise TypeError("Expected `other` to be iterable.") + + combined_items: List[_ResultItemT] = [*self._iter_data(), *other] + return self.__class__(data=combined_items) _ResultT = TypeVar("_ResultT", bound=Result[Any]) diff --git a/tests/http/test_response_result_model.py b/tests/http/test_response_result_model.py index 0e4c224..bba9fab 100644 --- a/tests/http/test_response_result_model.py +++ b/tests/http/test_response_result_model.py @@ -64,6 +64,7 @@ def __init__(self, data: List[SampleResultItem]) -> None: confidence: Final[int] = 3 confidences: Final[List[int]] = [3, 4] intentional_disabling: Final[bool] = True +other_id: Final[str] = "3ca9b73aee21fbf278a636709e0f8f03" @pytest.fixture @@ -562,49 +563,52 @@ def test_result_supports_len_with_single_result_item( assert len(result) == 1 -def test_result_supports_extending( +def test_result_add_supports_adding_other_result( mock_result_item: Dict[str, Any], ) -> None: - """Tests that `Result` support appending items by implementing `extend`.""" - input = {**mock_result_item} - data = [SampleResultItem(**input)] - result = SampleListResult(data=data) - - assert len(result) == 1 + """Tests that `Result` __add__ support adding items from other `Result`.""" + result = SampleListResult(data=[SampleResultItem(**mock_result_item)]) + other = SampleListResult( + data=[SampleResultItem(**{**mock_result_item, "id": other_id})] + ) - values = [SampleResultItem(**{**input, "confidence": 4})] - result.extend(values=values) + combined = result + other - assert len(result) == 2 + assert isinstance(combined, SampleListResult) + assert len(combined) == 2 + assert len(result) == 1 + assert len(other) == 1 -def test_result_supports_extending_from_other_result( +def test_result_add_supports_adding_other_iterable( mock_result_item: Dict[str, Any], ) -> None: - """Tests that `Result` support appending items from other `Result`.""" - input = {**mock_result_item} - data = [SampleResultItem(**input)] - result = SampleListResult(data=data) + """Tests that `Result` __add__ support adding items from other `Iterable`.""" + result = SampleListResult(data=[SampleResultItem(**mock_result_item)]) + other = [SampleResultItem(**{**mock_result_item, "id": other_id})] - assert len(result) == 1 + combined = result + other - values = SampleListResult(data=[SampleResultItem(**{**input, "confidence": 4})]) - result.extend(values=values) - - assert len(result) == 2 + assert isinstance(combined, SampleListResult) + assert len(combined) == 2 + assert len(result) == 1 + assert len(other) == 1 -def test_result_does_not_support_extending_with_single_result_item( - mock_result_item: Dict[str, Any], +@pytest.mark.parametrize( + "invalid_other", + [ + None, + 123, + object(), + ], +) +def test_result_add_raises_type_error_when_other_is_not_iterable( + mock_result_item: dict[str, Any], + invalid_other: Any, ) -> None: - """Tests that `Result` does not support appending items by implementing `extend`.""" - input = {**mock_result_item} - item = SampleResultItem(**input) - result = SampleSingleResult(item) - - assert len(result) == 1 + """Tests that `Result` __add__ raises a `TypeError` when `other` is not `Iterable`.""" + result = SampleListResult(data=[SampleResultItem(**mock_result_item)]) - values = [SampleResultItem(**{**input, "confidence": 4})] - result.extend(values=values) - - assert len(result) == 1 + with pytest.raises(TypeError): + result + invalid_other From 90337198dba4b3a314c4c18c53eb158b4b8721c3 Mon Sep 17 00:00:00 2001 From: lykmapipo Date: Sun, 21 Jun 2026 18:37:57 +0300 Subject: [PATCH 3/5] refactor(http): optimize Result length checking and return NotImplemented in __add__ This: - Refine `__add__` type fallback logic to follow idiomatic Python `+` protocol - Update type annotation of `other` in `__add__` from `Iterable[_ResultItemT]` to `object` - Optimize `__len__` by directly checking underlying `_data` --- src/gfwapiclient/http/models/response.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/gfwapiclient/http/models/response.py b/src/gfwapiclient/http/models/response.py index 1a2824a..356edc0 100644 --- a/src/gfwapiclient/http/models/response.py +++ b/src/gfwapiclient/http/models/response.py @@ -264,16 +264,19 @@ def __len__(self) -> int: int: The total number of items in API endpoint result data. """ - return len(list(self._iter_data())) + if isinstance(self._data, list): + return len(self._data) + + return 1 - def __add__(self, other: Iterable[_ResultItemT]) -> "Result[_ResultItemT]": + def __add__(self, other: object) -> "Result[_ResultItemT]": """Concatenates API endpoint result data with items from another iterable. This method returns a new `Result` instance containing combined `ResultItem` objects. Args: - other (Iterable[_ResultItemT]): + other (object): Iterable of `ResultItem` to append to API endpoint result data. Returns: @@ -284,11 +287,11 @@ def __add__(self, other: Iterable[_ResultItemT]) -> "Result[_ResultItemT]": TypeError: If `other` is not iterable. """ - if not isinstance(other, Iterable): - raise TypeError("Expected `other` to be iterable.") + if isinstance(other, Iterable): + combined_items: List[_ResultItemT] = [*self._iter_data(), *other] + return self.__class__(data=combined_items) - combined_items: List[_ResultItemT] = [*self._iter_data(), *other] - return self.__class__(data=combined_items) + return NotImplemented _ResultT = TypeVar("_ResultT", bound=Result[Any]) From 7b6613b91192cb4cf4836d9f23992ee483827408 Mon Sep 17 00:00:00 2001 From: lykmapipo Date: Mon, 22 Jun 2026 20:40:31 +0300 Subject: [PATCH 4/5] feat(http): add flat_map utility to Result This: - Add `flat_map()` utility to the `Result` class to allow transforming and flattening nested iterables - Clean up `Returns` docstrings from `Result` generator utilities --- src/gfwapiclient/http/models/response.py | 86 +++++++++++++--- tests/http/test_response_result_model.py | 122 ++++++++++++++++++++++- 2 files changed, 194 insertions(+), 14 deletions(-) diff --git a/src/gfwapiclient/http/models/response.py b/src/gfwapiclient/http/models/response.py index 356edc0..cf16c9b 100644 --- a/src/gfwapiclient/http/models/response.py +++ b/src/gfwapiclient/http/models/response.py @@ -12,11 +12,14 @@ Type, TypeVar, Union, + cast, ) import geopandas as gpd import pandas as pd +from pydantic import BaseModel as PydanticBaseModel + from gfwapiclient.base.models import BaseModel @@ -200,17 +203,13 @@ def map( Args: mapper (Callable[[_ResultItemT], _ResultItemMappedT]): - A callable that transform a `ResultItem` instance and + A callable that transforms a `ResultItem` instance and returns transformed `_ResultItemMappedT` instance. Yields: _ResultItemMappedT: Individual transformed `_ResultItemMappedT` from API endpoint result data. - Returns: - Iterator[_ResultItemMappedT]: - An iterator over transformed API endpoint result data. - Raises: TypeError: If `mapper` is not callable. @@ -221,6 +220,75 @@ def map( for item in self._iter_data(): yield mapper(item) + def flat_map( + self, + *, + mapper: Callable[ + [_ResultItemT], Union[_ResultItemMappedT, Iterable[_ResultItemMappedT]] + ], + ) -> Iterator[_ResultItemMappedT]: + """Transforms and flattens API endpoint result data using a mapping function. + + This method applies `mapper` to every `ResultItem`, recursively flattens + iterable values returned by the `mapper`, and yielding the transformed `_ResultItemMappedT`. + + Unlike :meth:`map`, which yields the mapper output directly, `flat_map` + expands nested collections into a single iterator of values. + + Iterable values are flattened when they represent collections, including: + + - `list` + - `tuple` + - `set` + - generators + - iterators + - other non-atomic iterable objects + + Atomic values are yielded unchanged. The following objects are treated as + scalar values and are not expanded: + + - `str` + - `bytes` + - `dict` + - `None` + - :class:`PydanticBaseModel` instances + - :class:`BaseModel` instances + - :class:`ResultItem` instances + + Args: + mapper (Callable[[_ResultItemT], Union[_ResultItemMappedT, Iterable[_ResultItemMappedT]]]): + A callable that transforms a `ResultItem` instance and + returns either transformed single or iterable of + `_ResultItemMappedT` instance. + + Yields: + _ResultItemMappedT: + Individual transformed-flattened `_ResultItemMappedT` from API endpoint + result data produced by the mapper. + + Raises: + TypeError: + If `mapper` is not callable. + """ + if not callable(mapper): + raise TypeError("Expected `mapper` to be callable.") + + def _yield_values( + value: object, + ) -> Iterator[_ResultItemMappedT]: + """Recursively yields flattened values from nested iterables.""" + if isinstance(value, Iterable) and not isinstance( + value, + (str, bytes, dict, ResultItem, BaseModel, PydanticBaseModel), + ): + for nested_value in value: + yield from _yield_values(nested_value) + else: + yield cast(_ResultItemMappedT, value) + + for item in self._iter_data(): + yield from _yield_values(mapper(item)) + def _iter_data(self) -> Iterator[_ResultItemT]: """Iterate lazily over API endpoint result data without copying. @@ -234,10 +302,6 @@ def _iter_data(self) -> Iterator[_ResultItemT]: Yields: _ResultItemT: Individual `ResultItem` contained in API endpoint result data. - - Returns: - Iterator[_ResultItemT]: - An iterator over API endpoint result data. """ if isinstance(self._data, list): yield from self._data @@ -250,10 +314,6 @@ def __iter__(self) -> Iterator[_ResultItemT]: Yields: _ResultItemT: Individual `ResultItem` contained in API endpoint result data. - - Returns: - Iterator[_ResultItemT]: - An iterator over API endpoint result data. """ yield from self._iter_data() diff --git a/tests/http/test_response_result_model.py b/tests/http/test_response_result_model.py index bba9fab..e61100c 100644 --- a/tests/http/test_response_result_model.py +++ b/tests/http/test_response_result_model.py @@ -503,7 +503,7 @@ def test_result_map_raises_type_error_for_non_callable_mapper( mock_result_item: Dict[str, Any], invalid_mapper: Any, ) -> None: - """Tests that `Result` raises a `TypeError` when `mapper` is not callable.""" + """Tests that `Result` map raises a `TypeError` when `mapper` is not callable.""" data = [SampleResultItem(**mock_result_item)] result = SampleListResult(data=data) @@ -511,6 +511,126 @@ def test_result_map_raises_type_error_for_non_callable_mapper( list(result.map(mapper=invalid_mapper)) +def test_result_flat_map_transforms_and_flattens_list_values( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` flat map transforms and flattens list values correctly.""" + data = [ + SampleResultItem(**mock_result_item), + SampleResultItem(**{**mock_result_item, "id": mock_result_item["id"] * 2}), + ] + + result = SampleListResult(data=data) + + def to_ids(item: SampleResultItem) -> List[str]: + return [item.id] + + mapped_result: Iterator[str] = result.flat_map(mapper=to_ids) + + assert mapped_result is not None + assert isinstance(mapped_result, Iterator) + + mapped_result_list: List[str] = list(mapped_result) + assert len(mapped_result_list) == 2 + assert mapped_result_list[0] == data[0].id + assert mapped_result_list[1] == data[1].id + + +def test_result_flat_map_transforms_and_flattens_generator_values( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` flat map transforms and flattens generator values correctly.""" + data = [ + SampleResultItem(**mock_result_item), + SampleResultItem(**{**mock_result_item, "id": mock_result_item["id"] * 2}), + ] + + result = SampleListResult(data=data) + + def to_ids(item: SampleResultItem) -> Iterator[str]: + yield item.id + + mapped_result: Iterator[str] = result.flat_map(mapper=to_ids) + + assert mapped_result is not None + assert isinstance(mapped_result, Iterator) + + mapped_result_list: List[str] = list(mapped_result) + assert len(mapped_result_list) == 2 + assert mapped_result_list[0] == data[0].id + assert mapped_result_list[1] == data[1].id + + +def test_result_flat_map_transforms_and_not_flattens_dict_values( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` flat map transforms and not flattens dict values correctly.""" + data = [ + SampleResultItem(**mock_result_item), + SampleResultItem(**{**mock_result_item, "id": mock_result_item["id"] * 2}), + ] + + result = SampleListResult(data=data) + + def to_dict(item: SampleResultItem) -> Iterator[Dict[str, Any]]: + yield {"id": item.id} + + mapped_result: Iterator[Dict[str, Any]] = result.flat_map(mapper=to_dict) + + assert mapped_result is not None + assert isinstance(mapped_result, Iterator) + + mapped_result_list: List[Dict[str, Any]] = list(mapped_result) + assert len(mapped_result_list) == 2 + assert isinstance(mapped_result_list[0], dict) + assert mapped_result_list[0] == {"id": data[0].id} + assert isinstance(mapped_result_list[1], dict) + assert mapped_result_list[1] == {"id": data[1].id} + + +def test_result_flat_map_transforms_and_not_flattens_pydantic_models( + mock_result_item: Dict[str, Any], +) -> None: + """Tests that `Result` flat map transforms and not flattens pydantic models correctly.""" + data = [ + SampleResultItem(**mock_result_item), + SampleResultItem(**{**mock_result_item, "id": mock_result_item["id"] * 2}), + ] + + result = SampleListResult(data=data) + + def to_model(item: SampleResultItem) -> Iterator[SampleResultItem]: + yield item + + mapped_result: Iterator[SampleResultItem] = result.flat_map(mapper=to_model) + + assert mapped_result is not None + assert isinstance(mapped_result, Iterator) + + mapped_result_list: List[SampleResultItem] = list(mapped_result) + assert len(mapped_result_list) == 2 + assert isinstance(mapped_result_list[0], SampleResultItem) + assert mapped_result_list[0] == data[0] + assert isinstance(mapped_result_list[1], SampleResultItem) + assert mapped_result_list[1] == data[1] + + +@pytest.mark.parametrize( + "invalid_mapper", + ["invalid", 123, object(), True, [], {}], +) +def test_result_flat_map_raises_type_error_for_non_callable_mapper( + mock_result_item: Dict[str, Any], + invalid_mapper: Any, +) -> None: + """Tests that `Result` flat map raises a `TypeError` when `mapper` is not callable.""" + data = [SampleResultItem(**mock_result_item)] + result = SampleListResult(data=data) + + with pytest.raises(TypeError): + list(result.flat_map(mapper=invalid_mapper)) + + def test_result_supports_iteration( mock_result_item: Dict[str, Any], ) -> None: From 6ae0428208edbb030cc63900b5de2ce1b214c51e Mon Sep 17 00:00:00 2001 From: lykmapipo Date: Fri, 26 Jun 2026 00:19:41 +0300 Subject: [PATCH 5/5] ci(github-actions): bump action versions for CI and pre-commit workflows This: - Upgrade `actions/checkout` from v4.2.2 to v7.0.0 - Upgrade `actions/setup-python` from v5.4.0 to v6.3.0 - Upgrade `codecov/codecov-action` from v5.3.1 to v7.0.0 --- .github/workflows/ci.yaml | 6 +++--- .github/workflows/pre-commit.yaml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4e3d55b..17ef941 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,11 +20,11 @@ jobs: steps: - name: Checkout codes id: checkout-codes - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v7.0.0 - name: Setup python id: setup-python - uses: actions/setup-python@v5.4.0 + uses: actions/setup-python@v6.3.0 with: python-version: ${{ matrix.python-version }} architecture: x64 @@ -43,7 +43,7 @@ jobs: - name: Upload coverage reports to Codecov id: upload-coverage-reports-to-codecov - uses: codecov/codecov-action@v5.3.1 + uses: codecov/codecov-action@v7.0.0 if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index f3ec3ce..7a56893 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -14,11 +14,11 @@ jobs: steps: - name: Checkout codes id: checkout-codes - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v7.0.0 - name: Setup python id: setup-python - uses: actions/setup-python@v5.4.0 + uses: actions/setup-python@v6.3.0 with: python-version: 3.12.8 architecture: x64