Skip to content

Commit df740f4

Browse files
authored
Merge pull request #15 from franccesco/fix/sdk-bugfixes-v0.21.1
fix: SDK bug fixes — truthiness guards, goal owner overwrite, meeting details rewrite
2 parents 19beff8 + a5e35c2 commit df740f4

36 files changed

Lines changed: 4986 additions & 320 deletions

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
run: uv sync --all-extras
4040

4141
- name: Run tests with coverage
42-
run: uv run pytest --cov=bloomy --cov-report=term-missing --cov-report=xml
42+
run: uv run pytest -m "not integration" --cov=bloomy --cov-report=term-missing --cov-report=xml
4343

4444
- name: Upload coverage report
4545
if: matrix.python-version == '3.12'

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "bloomy-python"
3-
version = "0.21.0"
3+
version = "0.21.1"
44
description = "Python SDK for Bloom Growth API"
55
readme = "README.md"
66
authors = [{ name = "Franccesco Orozco", email = "franccesco@codingdose.info" }]
@@ -79,6 +79,9 @@ reportMissingImports = true
7979
testpaths = ["tests"]
8080
pythonpath = ["src"]
8181
addopts = "-ra --strict-markers --cov=bloomy --cov-report=term-missing"
82+
markers = [
83+
"integration: marks tests that hit the real Bloom Growth API (deselect with '-m \"not integration\"')",
84+
]
8285

8386
[dependency-groups]
8487
dev = [

src/bloomy/configuration.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ def __init__(self, api_key: str | None = None) -> None:
3232
```
3333
3434
"""
35-
self.api_key = api_key or os.environ.get("BG_API_KEY") or self._load_api_key()
35+
stripped_key = api_key.strip() if api_key else api_key
36+
self.api_key = (
37+
stripped_key or os.environ.get("BG_API_KEY") or self._load_api_key()
38+
)
3639

3740
def configure_api_key(
3841
self, username: str, password: str, store_key: bool = False

src/bloomy/models.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ class UserSearchResult(BloomyBaseModel):
8888

8989
id: int
9090
name: str
91-
description: str
91+
description: str | None = None
9292
email: str
9393
organization_id: int
94-
image_url: str
94+
image_url: str | None = None
9595

9696

9797
class UserListItem(BloomyBaseModel):
@@ -100,8 +100,8 @@ class UserListItem(BloomyBaseModel):
100100
id: int
101101
name: str
102102
email: str
103-
position: str
104-
image_url: str
103+
position: str | None = None
104+
image_url: str | None = None
105105

106106

107107
class MeetingAttendee(BloomyBaseModel):
@@ -265,7 +265,7 @@ class CreatedGoalInfo(BloomyBaseModel):
265265
user_name: str
266266
title: str
267267
meeting_id: int
268-
meeting_title: str
268+
meeting_title: str | None = None
269269
status: str
270270
created_at: str
271271

src/bloomy/operations/async_/goals.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,14 @@ async def update(
103103
Args:
104104
goal_id: The ID of the goal to update
105105
title: The new title of the goal
106-
accountable_user: The ID of the user responsible for the goal
107-
(default: initialized user ID)
106+
accountable_user: The ID of the user responsible for the goal.
107+
If not provided, the existing owner is preserved.
108108
status: The status value. Can be a GoalStatus enum member or string
109109
('on', 'off', or 'complete'). Use GoalStatus.ON_TRACK,
110110
GoalStatus.AT_RISK, or GoalStatus.COMPLETE for type safety.
111111
Invalid values will raise ValueError via the update payload builder.
112112
113113
"""
114-
if accountable_user is None:
115-
accountable_user = await self.get_user_id()
116-
117114
payload = self._build_goal_update_payload(accountable_user, title, status)
118115

119116
response = await self._client.put(f"rocks/{goal_id}", json=payload)

src/bloomy/operations/async_/headlines.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ async def create(
5656
id=data["Id"],
5757
title=data["Name"],
5858
owner_details=OwnerDetails(id=owner_id, name=None),
59-
notes_url=data.get("DetailsUrl", ""),
59+
notes_url=data.get("DetailsUrl") or "",
6060
)
6161

6262
async def update(self, headline_id: int, title: str) -> None:
@@ -106,10 +106,10 @@ async def list(
106106
ValueError: If both user_id and meeting_id are provided
107107
108108
"""
109-
if user_id and meeting_id:
109+
if user_id is not None and meeting_id is not None:
110110
raise ValueError("Please provide either user_id or meeting_id, not both.")
111111

112-
if meeting_id:
112+
if meeting_id is not None:
113113
response = await self._client.get(f"l10/{meeting_id}/headlines")
114114
else:
115115
if user_id is None:

src/bloomy/operations/async_/issues.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ async def list(
5454
ValueError: When both user_id and meeting_id are provided
5555
5656
"""
57-
if user_id and meeting_id:
57+
if user_id is not None and meeting_id is not None:
5858
raise ValueError(
5959
"Please provide either `user_id` or `meeting_id`, not both."
6060
)
6161

62-
if meeting_id:
62+
if meeting_id is not None:
6363
response = await self._client.get(f"l10/{meeting_id}/issues")
6464
else:
6565
if user_id is None:

src/bloomy/operations/async_/meetings.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
import asyncio
66
import builtins
7-
from typing import TYPE_CHECKING, Any
7+
from typing import Any
88

9-
from ...exceptions import APIError
109
from ...models import (
1110
BulkCreateError,
1211
BulkCreateResult,
@@ -20,9 +19,6 @@
2019
from ...utils.async_base_operations import AsyncBaseOperations
2120
from ..mixins.meetings_transform import MeetingOperationsMixin
2221

23-
if TYPE_CHECKING:
24-
pass
25-
2622

2723
class AsyncMeetingOperations(AsyncBaseOperations, MeetingOperationsMixin):
2824
"""Async class to handle all operations related to meetings.
@@ -176,9 +172,6 @@ async def details(
176172
Returns:
177173
A MeetingDetails model instance with comprehensive meeting information
178174
179-
Raises:
180-
APIError: If the meeting with the given ID is not found
181-
182175
Example:
183176
```python
184177
await client.meeting.details(1)
@@ -187,11 +180,9 @@ async def details(
187180
```
188181
189182
"""
190-
meetings = await self.list()
191-
meeting = next((m for m in meetings if m.id == meeting_id), None)
192-
193-
if not meeting:
194-
raise APIError(f"Meeting with ID {meeting_id} not found", status_code=404)
183+
response = await self._client.get(f"L10/{meeting_id}")
184+
response.raise_for_status()
185+
data: Any = response.json()
195186

196187
# Fetch all sub-resources in parallel for better performance
197188
attendees_task = asyncio.create_task(self.attendees(meeting_id))
@@ -209,11 +200,11 @@ async def details(
209200
)
210201

211202
return MeetingDetails(
212-
id=meeting.id,
213-
name=meeting.name,
214-
start_date_utc=getattr(meeting, "start_date_utc", None),
215-
created_date=getattr(meeting, "created_date", None),
216-
organization_id=getattr(meeting, "organization_id", None),
203+
id=data["Id"],
204+
name=data.get("Basics", {}).get("Name", ""),
205+
start_date_utc=data.get("StartDateUtc"),
206+
created_date=data.get("CreateTime"),
207+
organization_id=data.get("OrganizationId"),
217208
attendees=attendees,
218209
issues=issues,
219210
todos=todos,

src/bloomy/operations/async_/scorecard.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ async def list(
5151
ValueError: If both user_id and meeting_id are provided
5252
5353
"""
54-
if user_id and meeting_id:
54+
if user_id is not None and meeting_id is not None:
5555
raise ValueError(
5656
"Please provide either `user_id` or `meeting_id`, not both."
5757
)
5858

59-
if meeting_id:
59+
if meeting_id is not None:
6060
response = await self._client.get(f"scorecard/meeting/{meeting_id}")
6161
else:
6262
if user_id is None:

src/bloomy/operations/async_/todos.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
import builtins
6-
from datetime import datetime
6+
from datetime import UTC, datetime
77
from typing import TYPE_CHECKING
88

99
from ...models import BulkCreateResult, Todo
@@ -120,7 +120,7 @@ async def create(
120120
"DetailsUrl": data.get("DetailsUrl"),
121121
"DueDate": data.get("DueDate"),
122122
"CompleteTime": None,
123-
"CreateTime": data.get("CreateTime", datetime.now().isoformat()),
123+
"CreateTime": data.get("CreateTime", datetime.now(UTC).isoformat()),
124124
"OriginId": meeting_id,
125125
"Origin": None,
126126
"Complete": False,

0 commit comments

Comments
 (0)