Skip to content

Commit 58d193b

Browse files
franccescoclaude
andcommitted
refactor(mixins): add transformation mixins with _transform suffix
- Create goals_transform.py, meetings_transform.py, issues_transform.py - Create headlines_transform.py, todos_transform.py - Rename users.py to users_transform.py for consistency - Update mixins/__init__.py exports Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8d82397 commit 58d193b

7 files changed

Lines changed: 498 additions & 1 deletion

File tree

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
"""Mixins for shared operation logic."""
22

3-
from .users import UserOperationsMixin
3+
from .goals_transform import GoalOperationsMixin
4+
from .headlines_transform import HeadlineOperationsMixin
5+
from .issues_transform import IssueOperationsMixin
6+
from .meetings_transform import MeetingOperationsMixin
7+
from .todos_transform import TodoOperationsMixin
8+
from .users_transform import UserOperationsMixin
49

510
__all__ = [
11+
"GoalOperationsMixin",
12+
"HeadlineOperationsMixin",
13+
"IssueOperationsMixin",
14+
"MeetingOperationsMixin",
15+
"TodoOperationsMixin",
616
"UserOperationsMixin",
717
]
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Mixin for shared goal operations logic."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, Any
6+
7+
from ...models import (
8+
ArchivedGoalInfo,
9+
CreatedGoalInfo,
10+
GoalInfo,
11+
GoalStatus,
12+
)
13+
14+
if TYPE_CHECKING:
15+
from collections.abc import Sequence
16+
17+
18+
class GoalOperationsMixin:
19+
"""Shared logic for goal operations."""
20+
21+
def _transform_goal_list(self, data: Sequence[dict[str, Any]]) -> list[GoalInfo]:
22+
"""Transform API response to list of GoalInfo models.
23+
24+
Args:
25+
data: The raw API response data.
26+
27+
Returns:
28+
A list of GoalInfo models.
29+
30+
"""
31+
return [
32+
GoalInfo(
33+
id=goal["Id"],
34+
user_id=goal["Owner"]["Id"],
35+
user_name=goal["Owner"]["Name"],
36+
title=goal["Name"],
37+
created_at=goal["CreateTime"],
38+
due_date=goal["DueDate"],
39+
status="Completed" if goal.get("Complete") else "Incomplete",
40+
meeting_id=goal["Origins"][0]["Id"] if goal.get("Origins") else None,
41+
meeting_title=(
42+
goal["Origins"][0]["Name"] if goal.get("Origins") else None
43+
),
44+
)
45+
for goal in data
46+
]
47+
48+
def _transform_archived_goals(
49+
self, data: Sequence[dict[str, Any]]
50+
) -> list[ArchivedGoalInfo]:
51+
"""Transform API response to list of ArchivedGoalInfo models.
52+
53+
Args:
54+
data: The raw API response data.
55+
56+
Returns:
57+
A list of ArchivedGoalInfo models.
58+
59+
"""
60+
return [
61+
ArchivedGoalInfo(
62+
id=goal["Id"],
63+
title=goal["Name"],
64+
created_at=goal["CreateTime"],
65+
due_date=goal["DueDate"],
66+
status="Complete" if goal.get("Complete") else "Incomplete",
67+
)
68+
for goal in data
69+
]
70+
71+
def _transform_created_goal(
72+
self, data: dict[str, Any], title: str, meeting_id: int, user_id: int
73+
) -> CreatedGoalInfo:
74+
"""Transform API response to CreatedGoalInfo model.
75+
76+
Args:
77+
data: The raw API response data.
78+
title: The title of the goal.
79+
meeting_id: The ID of the meeting associated with the goal.
80+
user_id: The ID of the user responsible for the goal.
81+
82+
Returns:
83+
A CreatedGoalInfo model.
84+
85+
"""
86+
# Map completion status
87+
completion_map = {2: "complete", 1: "on", 0: "off"}
88+
status = completion_map.get(data.get("Completion", 0), "off")
89+
90+
return CreatedGoalInfo(
91+
id=data["Id"],
92+
user_id=user_id,
93+
user_name=data["Owner"]["Name"],
94+
title=title,
95+
meeting_id=meeting_id,
96+
meeting_title=data["Origins"][0]["Name"],
97+
status=status,
98+
created_at=data["CreateTime"],
99+
)
100+
101+
def _build_goal_update_payload(
102+
self,
103+
accountable_user: int,
104+
title: str | None = None,
105+
status: GoalStatus | str | None = None,
106+
) -> dict[str, Any]:
107+
"""Build payload for goal update operation.
108+
109+
Args:
110+
accountable_user: The ID of the user responsible for the goal.
111+
title: The new title of the goal.
112+
status: The status value (GoalStatus enum or string).
113+
114+
Returns:
115+
A dictionary containing the update payload.
116+
117+
Raises:
118+
ValueError: If an invalid status value is provided.
119+
120+
"""
121+
payload: dict[str, Any] = {"accountableUserId": accountable_user}
122+
123+
if title is not None:
124+
payload["title"] = title
125+
126+
if status is not None:
127+
valid_status = {"on": "OnTrack", "off": "AtRisk", "complete": "Complete"}
128+
# Handle both GoalStatus enum and string
129+
status_value = status.value if isinstance(status, GoalStatus) else status
130+
status_key = status_value.lower()
131+
if status_key not in valid_status:
132+
raise ValueError(
133+
"Invalid status value. Must be 'on', 'off', or 'complete'."
134+
)
135+
payload["completion"] = valid_status[status_key]
136+
137+
return payload
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Mixin for shared headline operations logic."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, Any
6+
7+
from ...models import (
8+
HeadlineDetails,
9+
HeadlineListItem,
10+
MeetingInfo,
11+
OwnerDetails,
12+
)
13+
14+
if TYPE_CHECKING:
15+
from collections.abc import Sequence
16+
17+
18+
class HeadlineOperationsMixin:
19+
"""Shared logic for headline operations."""
20+
21+
def _transform_headline_details(self, data: dict[str, Any]) -> HeadlineDetails:
22+
"""Transform API response to HeadlineDetails model.
23+
24+
Args:
25+
data: The raw API response data.
26+
27+
Returns:
28+
A HeadlineDetails model.
29+
30+
"""
31+
return HeadlineDetails(
32+
id=data["Id"],
33+
title=data["Name"],
34+
notes_url=data["DetailsUrl"],
35+
meeting_details=MeetingInfo(
36+
id=data["OriginId"],
37+
title=data["Origin"],
38+
),
39+
owner_details=OwnerDetails(
40+
id=data["Owner"]["Id"],
41+
name=data["Owner"]["Name"],
42+
),
43+
archived=data["Archived"],
44+
created_at=data["CreateTime"],
45+
closed_at=data["CloseTime"],
46+
)
47+
48+
def _transform_headline_list(
49+
self, data: Sequence[dict[str, Any]]
50+
) -> list[HeadlineListItem]:
51+
"""Transform API response to list of HeadlineListItem models.
52+
53+
Args:
54+
data: The raw API response data.
55+
56+
Returns:
57+
A list of HeadlineListItem models.
58+
59+
"""
60+
return [
61+
HeadlineListItem(
62+
id=headline["Id"],
63+
title=headline["Name"],
64+
meeting_details=MeetingInfo(
65+
id=headline["OriginId"],
66+
title=headline["Origin"],
67+
),
68+
owner_details=OwnerDetails(
69+
id=headline["Owner"]["Id"],
70+
name=headline["Owner"]["Name"],
71+
),
72+
archived=headline["Archived"],
73+
created_at=headline["CreateTime"],
74+
closed_at=headline["CloseTime"],
75+
)
76+
for headline in data
77+
]
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Mixin for shared issue operations logic."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, Any
6+
7+
from ...models import (
8+
CreatedIssue,
9+
IssueDetails,
10+
IssueListItem,
11+
)
12+
13+
if TYPE_CHECKING:
14+
from collections.abc import Sequence
15+
16+
17+
class IssueOperationsMixin:
18+
"""Shared logic for issue operations."""
19+
20+
def _transform_issue_details(self, data: dict[str, Any]) -> IssueDetails:
21+
"""Transform API response to IssueDetails model.
22+
23+
Args:
24+
data: The raw API response data.
25+
26+
Returns:
27+
An IssueDetails model.
28+
29+
"""
30+
return IssueDetails(
31+
id=data["Id"],
32+
title=data["Name"],
33+
notes_url=data["DetailsUrl"],
34+
created_at=data["CreateTime"],
35+
completed_at=data["CloseTime"],
36+
meeting_id=data["OriginId"],
37+
meeting_title=data["Origin"],
38+
user_id=data["Owner"]["Id"],
39+
user_name=data["Owner"]["Name"],
40+
)
41+
42+
def _transform_issue_list(
43+
self, data: Sequence[dict[str, Any]]
44+
) -> list[IssueListItem]:
45+
"""Transform API response to list of IssueListItem models.
46+
47+
Args:
48+
data: The raw API response data.
49+
50+
Returns:
51+
A list of IssueListItem models.
52+
53+
"""
54+
return [
55+
IssueListItem(
56+
id=issue["Id"],
57+
title=issue["Name"],
58+
notes_url=issue["DetailsUrl"],
59+
created_at=issue["CreateTime"],
60+
meeting_id=issue["OriginId"],
61+
meeting_title=issue["Origin"],
62+
)
63+
for issue in data
64+
]
65+
66+
def _transform_created_issue(self, data: dict[str, Any]) -> CreatedIssue:
67+
"""Transform API response to CreatedIssue model.
68+
69+
Args:
70+
data: The raw API response data.
71+
72+
Returns:
73+
A CreatedIssue model.
74+
75+
"""
76+
return CreatedIssue(
77+
id=data["Id"],
78+
meeting_id=data["OriginId"],
79+
meeting_title=data["Origin"],
80+
title=data["Name"],
81+
user_id=data["Owner"]["Id"],
82+
notes_url=data["DetailsUrl"],
83+
)

0 commit comments

Comments
 (0)