Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
98 changes: 95 additions & 3 deletions src/gfwapiclient/resources/events/base/models/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import datetime

from typing import Any, List, Optional
from typing import Any, Iterator, List, Optional, Type, TypeVar, Union

from pydantic import Field, field_validator

from gfwapiclient.base.models import BaseModel
from gfwapiclient.http.models import ResultItem
from gfwapiclient.http.models import Result, ResultItem


__all__ = ["EventItem"]
__all__ = ["EventItem", "EventResult"]


class EventPosition(BaseModel):
Expand Down Expand Up @@ -448,3 +448,95 @@ def empty_datetime_str_to_none(cls, value: Any) -> Optional[Any]:
if isinstance(value, str) and value.strip() == "":
return None
return value


_EventItemT = TypeVar("_EventItemT", bound=EventItem)


class EventResult(Result[_EventItemT]):
"""Result for the Events API endpoints.

This class extends :class:`Result` to provide a specialized result container
for the Events API endpoints.
"""

_result_item_class: Type[_EventItemT]
_data: Union[List[_EventItemT], _EventItemT]

def __init__(self, *, data: Union[List[_EventItemT], _EventItemT]) -> None:
"""Initializes a new `EventResult`.

Args:
data (Union[List[_EventItemT], _EventItemT]):
The response data from the Events API endpoint, which can
be either a single `ResultItem` or a list of `ResultItem` instances.
"""
super().__init__(data=data)

@property
def vessel_ids(self) -> List[str]:
"""Returns AIS vessel identifiers (IDs).

Returns:
List[str]:
Valid list of AIS vessel identifier (ID).
"""

def extract_vessel_ids(item: _EventItemT) -> Iterator[Optional[str]]:
if item.vessel:
yield item.vessel.id

mapped_vessel_ids: Iterator[Optional[str]] = self.flat_map(
mapper=extract_vessel_ids
)
matched_vessel_ids: List[str] = list(
{_vessel_id.strip() for _vessel_id in mapped_vessel_ids if _vessel_id}
)

return matched_vessel_ids

@property
def start_dates(self) -> List[datetime.date]:
"""Returns events start dates.

Returns:
List[str]:
Valid list of events start date.
"""

def extract_start_date(
item: _EventItemT,
) -> Iterator[Optional[datetime.datetime]]:
yield item.start

mapped_start_dates: Iterator[Optional[datetime.datetime]] = self.flat_map(
mapper=extract_start_date
)
matched_start_dates: List[datetime.date] = list(
{_start_date.date() for _start_date in mapped_start_dates if _start_date}
)

return matched_start_dates

@property
def end_dates(self) -> List[datetime.date]:
"""Returns events end dates.

Returns:
List[str]:
Valid list of events end date.
"""

def extract_end_date(
item: _EventItemT,
) -> Iterator[Optional[datetime.datetime]]:
yield item.end

mapped_end_date: Iterator[Optional[datetime.datetime]] = self.flat_map(
mapper=extract_end_date
)
matched_end_date: List[datetime.date] = list(
{_end_date.date() for _end_date in mapped_end_date if _end_date}
)

return matched_end_date
5 changes: 2 additions & 3 deletions src/gfwapiclient/resources/events/detail/models/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

from typing import Type

from gfwapiclient.http.models import Result
from gfwapiclient.resources.events.base.models.response import EventItem
from gfwapiclient.resources.events.base.models.response import EventItem, EventResult


__all__ = ["EventDetailItem", "EventDetailResult"]
Expand All @@ -15,7 +14,7 @@ class EventDetailItem(EventItem):
pass


class EventDetailResult(Result[EventDetailItem]):
class EventDetailResult(EventResult[EventDetailItem]):
"""Result containing the details of a single event."""

_result_item_class: Type[EventDetailItem]
Expand Down
5 changes: 2 additions & 3 deletions src/gfwapiclient/resources/events/list/models/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

from typing import List, Type

from gfwapiclient.http.models import Result
from gfwapiclient.resources.events.base.models.response import EventItem
from gfwapiclient.resources.events.base.models.response import EventItem, EventResult


__all__ = ["EventListItem", "EventListResult"]
Expand All @@ -15,7 +14,7 @@ class EventListItem(EventItem):
pass


class EventListResult(Result[EventListItem]):
class EventListResult(EventResult[EventListItem]):
"""Result containing a list of event items."""

_result_item_class: Type[EventListItem]
Expand Down
1 change: 1 addition & 0 deletions tests/resources/events/base/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for `gfwapiclient.resources.events.base`."""
1 change: 1 addition & 0 deletions tests/resources/events/base/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for `gfwapiclient.resources.events.base.models`."""
68 changes: 68 additions & 0 deletions tests/resources/events/base/models/test_response_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Tests for `gfwapiclient.resources.events.base.models.response`."""

from typing import Any, Dict, List

import pytest

from gfwapiclient.resources.events.base.models.response import (
EventItem,
EventResult,
)


def test_event_result_vessel_ids_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventResult` returns list of vessel ids correctly."""
data: List[EventItem] = [EventItem(**mock_raw_event_list_item)]
result = EventResult(data=data)
assert result.vessel_ids is not None
assert isinstance(result.vessel_ids, list)
assert len(result.vessel_ids) >= 1


@pytest.mark.parametrize(
"update",
[
{"vessel": None},
{"vessel": {"id": None}},
{"vessel": {"id": ""}},
{"vessel": {"id": " "}},
],
)
def test_event_result_vessel_ids_returns_empty_list_when_vessel_or_id_missing(
mock_raw_event_list_item: Dict[str, Any],
update: Dict[str, Any],
) -> None:
"""Test that `EventResult` vessel ids returns empty list when vessel or id missing."""
mocked_raw_event_list_item: Dict[str, Any] = {**mock_raw_event_list_item}
for k, v in update.items():
mocked_raw_event_list_item[k] = v

data: List[EventItem] = [EventItem(**mocked_raw_event_list_item)]
result = EventResult(data=data)
assert result.vessel_ids is not None
assert isinstance(result.vessel_ids, list)
assert len(result.vessel_ids) == 0


def test_event_result_start_dates_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventResult` start dates returns list of start dates correctly."""
data: List[EventItem] = [EventItem(**mock_raw_event_list_item)]
result = EventResult(data=data)
assert result.start_dates is not None
assert isinstance(result.start_dates, list)
assert len(result.start_dates) >= 1


def test_event_result_end_dates_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventResult` end dates to returns list of end dates correctly."""
data: List[EventItem] = [EventItem(**mock_raw_event_list_item)]
result = EventResult(data=data)
assert result.end_dates is not None
assert isinstance(result.end_dates, list)
assert len(result.end_dates) == 0
33 changes: 33 additions & 0 deletions tests/resources/events/detail/models/test_response_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,36 @@ def test_event_detail_result_deserializes_all_fields(
data: EventDetailItem = EventDetailItem(**mock_raw_event_detail_item)
result = EventDetailResult(data=data)
assert cast(EventDetailItem, result.data()) == data


def test_event_detail_result_vessel_ids_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventDetailResult` returns list of vessel ids correctly."""
data: EventDetailItem = EventDetailItem(**mock_raw_event_list_item)
result = EventDetailResult(data=data)
assert result.vessel_ids is not None
assert isinstance(result.vessel_ids, list)
assert len(result.vessel_ids) >= 1


def test_event_detail_result_start_dates_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventDetailResult` start dates returns list of start dates correctly."""
data: EventDetailItem = EventDetailItem(**mock_raw_event_list_item)
result = EventDetailResult(data=data)
assert result.start_dates is not None
assert isinstance(result.start_dates, list)
assert len(result.start_dates) >= 1


def test_event_detail_result_end_dates_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventDetailResult` end dates to returns list of end dates correctly."""
data: EventDetailItem = EventDetailItem(**mock_raw_event_list_item)
result = EventDetailResult(data=data)
assert result.end_dates is not None
assert isinstance(result.end_dates, list)
assert len(result.end_dates) == 0
33 changes: 33 additions & 0 deletions tests/resources/events/list/models/test_response_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,36 @@ def test_event_list_result_deserializes_all_fields(
data: List[EventListItem] = [EventListItem(**mock_raw_event_list_item)]
result = EventListResult(data=data)
assert cast(List[EventListItem], result.data()) == data


def test_event_list_result_vessel_ids_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventListResult` returns list of vessel ids correctly."""
data: List[EventListItem] = [EventListItem(**mock_raw_event_list_item)]
result = EventListResult(data=data)
assert result.vessel_ids is not None
assert isinstance(result.vessel_ids, list)
assert len(result.vessel_ids) >= 1


def test_event_list_result_start_dates_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventListResult` start dates returns list of start dates correctly."""
data: List[EventListItem] = [EventListItem(**mock_raw_event_list_item)]
result = EventListResult(data=data)
assert result.start_dates is not None
assert isinstance(result.start_dates, list)
assert len(result.start_dates) >= 1


def test_event_list_result_end_dates_returns_correctly(
mock_raw_event_list_item: Dict[str, Any],
) -> None:
"""Test that `EventListResult` end dates to returns list of end dates correctly."""
data: List[EventListItem] = [EventListItem(**mock_raw_event_list_item)]
result = EventListResult(data=data)
assert result.end_dates is not None
assert isinstance(result.end_dates, list)
assert len(result.end_dates) == 0
Loading