diff --git a/pyproject.toml b/pyproject.toml index 66cb2da..7175e4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "bloomy-python" -version = "0.21.2" +version = "0.22.0" description = "Python SDK for Bloom Growth API" readme = "README.md" authors = [{ name = "Franccesco Orozco", email = "franccesco@codingdose.info" }] diff --git a/src/bloomy/models.py b/src/bloomy/models.py index 061fe51..1705323 100644 --- a/src/bloomy/models.py +++ b/src/bloomy/models.py @@ -36,6 +36,8 @@ def _parse_optional_float(v: Any) -> float | None: # Reusable annotated types for optional fields that may come as empty strings OptionalDatetime = Annotated[datetime | None, BeforeValidator(_parse_optional_datetime)] OptionalFloat = Annotated[float | None, BeforeValidator(_parse_optional_float)] +# Coerces a nullable value (e.g. a datetime or None) into a bool (present = True) +IsPresent = Annotated[bool, BeforeValidator(lambda v: v is not None and v != "")] class GoalStatus(StrEnum): @@ -157,6 +159,7 @@ class Todo(BloomyBaseModel): meeting_id: int | None = Field(alias="OriginId", default=None) meeting_name: str | None = Field(alias="Origin", default=None) complete: bool = Field(alias="Complete", default=False) + archived: IsPresent = Field(alias="CloseTime", default=False) class Issue(BloomyBaseModel): @@ -301,6 +304,7 @@ class IssueDetails(BloomyBaseModel): notes_url: str created_at: str completed_at: str | None = None + archived: bool = False meeting_id: int meeting_title: str user_id: int @@ -314,6 +318,7 @@ class IssueListItem(BloomyBaseModel): title: str notes_url: str created_at: str + archived: bool = False meeting_id: int meeting_title: str | None diff --git a/src/bloomy/operations/async_/todos.py b/src/bloomy/operations/async_/todos.py index ddd7f40..30863e2 100644 --- a/src/bloomy/operations/async_/todos.py +++ b/src/bloomy/operations/async_/todos.py @@ -120,6 +120,7 @@ async def create( "DetailsUrl": data.get("DetailsUrl"), "DueDate": data.get("DueDate"), "CompleteTime": None, + "CloseTime": None, "CreateTime": data.get("CreateTime", datetime.now(UTC).isoformat()), "OriginId": meeting_id, "Origin": None, diff --git a/src/bloomy/operations/mixins/issues_transform.py b/src/bloomy/operations/mixins/issues_transform.py index 63c92e5..54fe9e5 100644 --- a/src/bloomy/operations/mixins/issues_transform.py +++ b/src/bloomy/operations/mixins/issues_transform.py @@ -33,6 +33,7 @@ def _transform_issue_details(self, data: dict[str, Any]) -> IssueDetails: notes_url=data["DetailsUrl"], created_at=data["CreateTime"], completed_at=data["CloseTime"], + archived=data["Archived"], meeting_id=data["OriginId"], meeting_title=data["Origin"], user_id=data["Owner"]["Id"], @@ -57,6 +58,7 @@ def _transform_issue_list( title=issue["Name"], notes_url=issue["DetailsUrl"], created_at=issue["CreateTime"], + archived=issue["Archived"], meeting_id=issue["OriginId"], meeting_title=issue["Origin"], ) diff --git a/src/bloomy/operations/todos.py b/src/bloomy/operations/todos.py index d7f1c97..d295084 100644 --- a/src/bloomy/operations/todos.py +++ b/src/bloomy/operations/todos.py @@ -120,6 +120,7 @@ def create( "DetailsUrl": data.get("DetailsUrl"), "DueDate": data.get("DueDate"), "CompleteTime": None, + "CloseTime": None, "CreateTime": data.get("CreateTime", datetime.now(UTC).isoformat()), "OriginId": meeting_id, "Origin": None, diff --git a/tests/conftest.py b/tests/conftest.py index e00da24..7213f61 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -107,6 +107,7 @@ def sample_todo_data() -> dict[str, Any]: "Complete": False, "CreateTime": "2024-01-01T10:00:00Z", "CompleteTime": None, + "CloseTime": None, "Owner": {"Id": 123, "Name": "John Doe"}, } @@ -170,6 +171,7 @@ def sample_issue_data() -> dict[str, Any]: "CreateTime": "2024-06-01T10:00:00Z", "CloseTime": None, "Complete": False, + "Archived": False, "Owner": {"Id": 123, "Name": "John Doe"}, "Origin": "Infrastructure Meeting", "OriginId": 456, diff --git a/tests/test_adversarial_issues_headlines.py b/tests/test_adversarial_issues_headlines.py index ef31ccf..b49bf7d 100644 --- a/tests/test_adversarial_issues_headlines.py +++ b/tests/test_adversarial_issues_headlines.py @@ -389,6 +389,7 @@ def test_owner_is_none_raises_type_error(self) -> None: "DetailsUrl": "http://example.com", "CreateTime": "2024-01-01", "CloseTime": None, + "Archived": False, "OriginId": 10, "Origin": "Meeting", "Owner": None, @@ -405,6 +406,7 @@ def test_close_time_none_succeeds(self) -> None: "DetailsUrl": "http://example.com", "CreateTime": "2024-01-01", "CloseTime": None, + "Archived": False, "OriginId": 10, "Origin": "Meeting", "Owner": {"Id": 1, "Name": "John"}, @@ -421,6 +423,7 @@ def test_close_time_empty_string(self) -> None: "DetailsUrl": "http://example.com", "CreateTime": "2024-01-01", "CloseTime": "", + "Archived": False, "OriginId": 10, "Origin": "Meeting", "Owner": {"Id": 1, "Name": "John"}, diff --git a/tests/test_issues.py b/tests/test_issues.py index 480cc95..19ccf3c 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -41,6 +41,7 @@ def test_list_user_issues(self, mock_http_client: Mock, mock_user_id: Mock) -> N "Origin": "Infrastructure Meeting", "OriginId": 456, "DetailsUrl": "https://example.com/issue/401", + "Archived": False, } ] mock_http_client.get.return_value = mock_response @@ -68,6 +69,7 @@ def test_list_meeting_issues(self, mock_http_client: Mock) -> None: "Origin": "Infrastructure Meeting", "OriginId": 456, "DetailsUrl": "https://example.com/issue/401", + "Archived": False, } ] mock_http_client.get.return_value = mock_response