Skip to content
Merged
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
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.20.0] - 2025-12-10

### Added

- `client.issue.update()` method for updating existing issues
- `client.scorecard.get()` method for retrieving scorecard details
- `GoalStatus` enum for type-safe status values
- `base_url` and `timeout` parameters to sync `Client` for configuration flexibility
- AsyncClient now validates API key at initialization

### Changed

- **BREAKING**: `client.user.all()` renamed to `client.user.list()` for consistency
- **BREAKING**: `client.user.details(all=True)` renamed to `client.user.details(include_all=True)` for clarity
- **BREAKING**: `client.issue.solve()` renamed to `client.issue.complete()` and now returns `IssueDetails`
- **BREAKING**: `client.todo.complete()` now returns `Todo` instead of `bool`
- **BREAKING**: `client.goal.update/delete/archive/restore()` now return `None` instead of `bool`
- **BREAKING**: `client.headline.update/delete()` now return `None` instead of `bool`
- **BREAKING**: Client raises `ConfigurationError` instead of `ValueError` for missing API key

### Fixed

- All documentation updated to reflect the breaking changes

## [0.19.0] - 2025-12-10

### Fixed
Expand Down Expand Up @@ -192,7 +216,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Configuration management with multiple API key sources
- httpx-based HTTP client with bearer token authentication

[Unreleased]: https://github.com/franccesco/bloomy-python/compare/v0.19.0...HEAD
[Unreleased]: https://github.com/franccesco/bloomy-python/compare/v0.20.0...HEAD
[0.20.0]: https://github.com/franccesco/bloomy-python/compare/v0.19.0...v0.20.0
[0.19.0]: https://github.com/franccesco/bloomy-python/compare/v0.18.0...v0.19.0
[0.18.0]: https://github.com/franccesco/bloomy-python/compare/v0.17.0...v0.18.0
[0.17.0]: https://github.com/franccesco/bloomy-python/compare/v0.16.0...v0.17.0
Expand Down
53 changes: 52 additions & 1 deletion docs/api/async_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,55 @@
- __aenter__
- __aexit__
- close
heading_level: 2
heading_level: 2

## Basic Usage

```python
import asyncio
from bloomy import AsyncClient, ConfigurationError

async def main():
# Initialize with API key
async with AsyncClient(api_key="your-api-key") as client:
users = await client.user.list()

# Custom base URL (for testing/staging)
async with AsyncClient(
api_key="your-api-key",
base_url="https://staging.example.com/api/v1"
) as client:
users = await client.user.list()

# Custom timeout (in seconds)
async with AsyncClient(api_key="your-api-key", timeout=60.0) as client:
users = await client.user.list()

# Handle missing API key
try:
client = AsyncClient()
except ConfigurationError as e:
print(f"Configuration error: {e}")

asyncio.run(main())
```

## Parameters

- **api_key** (str, optional): Your Bloom Growth API key. If not provided, will attempt to load from `BG_API_KEY` environment variable or `~/.bloomy/config.yaml`
- **base_url** (str, optional): Custom API endpoint. Defaults to `"https://app.bloomgrowth.com/api/v1"`
- **timeout** (float, optional): Request timeout in seconds. Defaults to `30.0`

## Exceptions

- **ConfigurationError**: Raised when no API key can be found from any source

## Context Manager

The AsyncClient supports async context manager protocol for automatic resource cleanup:

```python
async with AsyncClient(api_key="your-api-key") as client:
users = await client.user.list()
# Client automatically closes when exiting the context
```
46 changes: 45 additions & 1 deletion docs/api/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,48 @@ The main entry point for interacting with the Bloomy API.
show_root_heading: true
show_root_full_path: false
members_order: source
show_signature_annotations: true
show_signature_annotations: true

## Basic Usage

```python
from bloomy import Client, ConfigurationError

# Initialize with API key
client = Client(api_key="your-api-key")

# Custom base URL (for testing/staging)
client = Client(
api_key="your-api-key",
base_url="https://staging.example.com/api/v1"
)

# Custom timeout (in seconds)
client = Client(api_key="your-api-key", timeout=60.0)

# Handle missing API key
try:
client = Client()
except ConfigurationError as e:
print(f"Configuration error: {e}")
```

## Parameters

- **api_key** (str, optional): Your Bloom Growth API key. If not provided, will attempt to load from `BG_API_KEY` environment variable or `~/.bloomy/config.yaml`
- **base_url** (str, optional): Custom API endpoint. Defaults to `"https://app.bloomgrowth.com/api/v1"`
- **timeout** (float, optional): Request timeout in seconds. Defaults to `30.0`

## Exceptions

- **ConfigurationError**: Raised when no API key can be found from any source

## Context Manager

The Client supports context manager protocol for automatic resource cleanup:

```python
with Client(api_key="your-api-key") as client:
users = client.user.list()
# Client automatically closes when exiting the context
```
76 changes: 52 additions & 24 deletions docs/api/operations/goals.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,81 +28,106 @@ The async version `AsyncGoalOperations` provides the same methods as above, but
!!! info "Async Usage"
All methods have the same parameters and return types as their sync counterparts. Simply add `await` before each method call.

## Goal Status Enum

The SDK provides a `GoalStatus` enum for type-safe status updates:

```python
from bloomy import GoalStatus

# Enum values (recommended)
GoalStatus.ON_TRACK # "on" - Goal is on track
GoalStatus.AT_RISK # "off" - Goal is at risk
GoalStatus.COMPLETE # "complete" - Goal is complete

# You can still use strings directly
client.goal.update(goal_id=123, status="on")
```

!!! tip "Using the Enum"
Using `GoalStatus` enum values is recommended for better type checking and IDE autocomplete support.

## Usage Examples

=== "Sync"

```python
from bloomy import Client
from bloomy import Client, GoalStatus

with Client(api_key="your-api-key") as client:
# List active goals
goals = client.goal.list()
for goal in goals:
print(f"{goal.title} - Status: {goal.status}")

# List with archived goals included
all_goals = client.goal.list(archived=True)
print(f"Active: {len(all_goals.active)}")
print(f"Archived: {len(all_goals.archived)}")

# Create a new goal
new_goal = client.goal.create(
title="Increase customer retention by 20%",
meeting_id=123
)
# Update goal status

# Update goal status using enum (recommended)
client.goal.update(
goal_id=new_goal.id,
status="on" # on track
status=GoalStatus.ON_TRACK
)

# Archive and restore

# Or use string directly
client.goal.update(goal_id=new_goal.id, status="off") # at risk

# Archive and restore (both return None)
client.goal.archive(goal_id=new_goal.id)
client.goal.restore(goal_id=new_goal.id)
# Delete a goal

# Delete a goal (returns None)
client.goal.delete(goal_id=new_goal.id)
```

=== "Async"

```python
import asyncio
from bloomy import AsyncClient
from bloomy import AsyncClient, GoalStatus

async def main():
async with AsyncClient(api_key="your-api-key") as client:
# List active goals
goals = await client.goal.list()
for goal in goals:
print(f"{goal.title} - Status: {goal.status}")

# List with archived goals included
all_goals = await client.goal.list(archived=True)
print(f"Active: {len(all_goals.active)}")
print(f"Archived: {len(all_goals.archived)}")

# Create a new goal
new_goal = await client.goal.create(
title="Increase customer retention by 20%",
meeting_id=123
)
# Update goal status

# Update goal status using enum (recommended)
await client.goal.update(
goal_id=new_goal.id,
status="on" # on track
status=GoalStatus.ON_TRACK
)

# Archive and restore

# Or use string directly
await client.goal.update(goal_id=new_goal.id, status="off") # at risk

# Archive and restore (both return None)
await client.goal.archive(goal_id=new_goal.id)
await client.goal.restore(goal_id=new_goal.id)
# Delete a goal

# Delete a goal (returns None)
await client.goal.delete(goal_id=new_goal.id)

asyncio.run(main())
```

Expand All @@ -118,4 +143,7 @@ The async version `AsyncGoalOperations` provides the same methods as above, but
| `restore()` | Restore an archived goal | `goal_id` |

!!! info "Status Values"
Valid status values are: `'on'` (On Track), `'off'` (At Risk), or `'complete'` (Completed)
Valid status values are: `'on'` (On Track), `'off'` (At Risk), or `'complete'` (Completed). Use the `GoalStatus` enum for type-safe updates.

!!! note "Return Values"
The `update()`, `delete()`, `archive()`, and `restore()` methods return `None` instead of boolean values.
17 changes: 10 additions & 7 deletions docs/api/operations/headlines.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ The async version `AsyncHeadlineOperations` provides the same methods as above,
# List headlines for a specific meeting
meeting_headlines = client.headline.list(meeting_id=123)

# Update headline title
# Update headline title (returns None)
client.headline.update(
headline_id=headline.id,
title="Product launch exceeded expectations"
)
# Delete headline

# Delete headline (returns None)
client.headline.delete(headline_id=headline.id)
```

Expand Down Expand Up @@ -90,13 +90,13 @@ The async version `AsyncHeadlineOperations` provides the same methods as above,
# List headlines for a specific meeting
meeting_headlines = await client.headline.list(meeting_id=123)

# Update headline title
# Update headline title (returns None)
await client.headline.update(
headline_id=headline.id,
title="Product launch exceeded expectations"
)
# Delete headline

# Delete headline (returns None)
await client.headline.delete(headline_id=headline.id)

asyncio.run(main())
Expand All @@ -113,4 +113,7 @@ The async version `AsyncHeadlineOperations` provides the same methods as above,
| `delete()` | Delete a headline | `headline_id` |

!!! note "Filtering"
Like todos, headlines can be filtered by either `user_id` or `meeting_id`, but not both.
Like todos, headlines can be filtered by either `user_id` or `meeting_id`, but not both.

!!! note "Return Values"
The `update()` and `delete()` methods return `None` instead of boolean values.
Loading