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
62 changes: 62 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Code Quality

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

concurrency:
group: quality-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install dependencies
run: uv sync --all-extras

- name: Check formatting
run: uv run ruff format . --check

- name: Lint
run: uv run ruff check .

typecheck:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install dependencies
run: uv sync --all-extras

- name: Type check
run: uv run pyright
49 changes: 49 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Tests

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.12', '3.13']

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install dependencies
run: uv sync --all-extras

- name: Run tests with coverage
run: uv run pytest --cov=bloomy --cov-report=term-missing --cov-report=xml

- name: Upload coverage report
if: matrix.python-version == '3.12'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
16 changes: 7 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: local

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.3
hooks:
- id: standardrb
name: standardrb
description: Enforce the community Ruby Style Guide with standardrb
entry: standardrb
language: ruby
types: ["ruby"]
args: ["--fix"]
- id: ruff
args: [--fix]
- id: ruff-format
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "bloomy-python"
version = "0.18.0"
version = "0.19.0"
description = "Python SDK for Bloom Growth API"
readme = "README.md"
authors = [{ name = "Franccesco Orozco", email = "franccesco@codingdose.info" }]
Expand Down Expand Up @@ -70,7 +70,7 @@ quote-style = "double"
indent-style = "space"

[tool.pyright]
include = ["src", "tests"]
include = ["src"]
pythonVersion = "3.12"
typeCheckingMode = "strict"
reportMissingImports = true
Expand Down
40 changes: 22 additions & 18 deletions src/bloomy/operations/async_/meetings.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ async def attendees(self, meeting_id: int) -> builtins.list[MeetingAttendee]:

# Map Id to UserId for compatibility
return [
MeetingAttendee.model_validate({
"UserId": attendee["Id"],
"Name": attendee["Name"],
"ImageUrl": attendee["ImageUrl"]
})
MeetingAttendee.model_validate(
{
"UserId": attendee["Id"],
"Name": attendee["Name"],
"ImageUrl": attendee["ImageUrl"],
}
)
for attendee in data
]

Expand Down Expand Up @@ -126,19 +128,21 @@ async def issues(

# Map meeting issue format to Issue model format
return [
Issue.model_validate({
"Id": issue["Id"],
"Name": issue["Name"],
"DetailsUrl": issue.get("DetailsUrl"),
"CreateDate": issue["CreateTime"],
"MeetingId": issue["OriginId"],
"MeetingName": issue["Origin"],
"OwnerName": issue["Owner"]["Name"],
"OwnerId": issue["Owner"]["Id"],
"OwnerImageUrl": issue["Owner"]["ImageUrl"],
"ClosedDate": issue.get("CloseTime"),
"CompletionDate": issue.get("CompleteTime"),
})
Issue.model_validate(
{
"Id": issue["Id"],
"Name": issue["Name"],
"DetailsUrl": issue.get("DetailsUrl"),
"CreateDate": issue["CreateTime"],
"MeetingId": issue["OriginId"],
"MeetingName": issue["Origin"],
"OwnerName": issue["Owner"]["Name"],
"OwnerId": issue["Owner"]["Id"],
"OwnerImageUrl": issue["Owner"]["ImageUrl"],
"ClosedDate": issue.get("CloseTime"),
"CompletionDate": issue.get("CompleteTime"),
}
)
for issue in data
]

Expand Down
2 changes: 1 addition & 1 deletion src/bloomy/operations/async_/todos.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ async def create(
payload["dueDate"] = due_date

if meeting_id is not None:
# Meeting todo - use the correct endpoint
# Meeting todo - use the correct endpoint with PascalCase keys
payload = {
"Title": title,
"ForId": user_id,
Expand Down
35 changes: 28 additions & 7 deletions src/bloomy/operations/todos.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def list(
def create(
self,
title: str,
meeting_id: int,
meeting_id: int | None = None,
due_date: str | None = None,
user_id: int | None = None,
notes: str | None = None,
Expand All @@ -74,7 +74,7 @@ def create(

Args:
title: The title of the new todo
meeting_id: The ID of the meeting associated with the todo
meeting_id: The ID of the meeting associated with the todo (optional)
due_date: The due date of the todo (optional)
user_id: The ID of the user responsible for the todo
(default: initialized user ID)
Expand All @@ -85,10 +85,15 @@ def create(

Example:
```python
# Create a user todo
client.todo.create(
title="New Todo", meeting_id=1, due_date="2024-06-15"
title="New Todo", due_date="2024-06-15"
)

# Create a meeting todo
client.todo.create(
title="Meeting Action", meeting_id=1, due_date="2024-06-15"
)
# Returns: Todo(id=1, name='New Todo', due_date='2024-06-15', ...)
```

"""
Expand All @@ -98,13 +103,29 @@ def create(
payload: dict[str, Any] = {
"title": title,
"accountableUserId": user_id,
"notes": notes,
}

if notes is not None:
payload["notes"] = notes

if due_date is not None:
payload["dueDate"] = due_date

response = self._client.post(f"L10/{meeting_id}/todos", json=payload)
if meeting_id is not None:
# Meeting todo - use the correct endpoint
payload = {
"Title": title,
"ForId": user_id,
}
if notes is not None:
payload["Notes"] = notes
if due_date is not None:
payload["dueDate"] = due_date
response = self._client.post(f"L10/{meeting_id}/todos", json=payload)
else:
# User todo
response = self._client.post("todo/create", json=payload)

response.raise_for_status()
data = response.json()

Expand All @@ -115,7 +136,7 @@ def create(
"DetailsUrl": data.get("DetailsUrl"),
"DueDate": data.get("DueDate"),
"CompleteTime": None,
"CreateTime": datetime.now().isoformat(),
"CreateTime": data.get("CreateTime", datetime.now().isoformat()),
"OriginId": meeting_id,
"Origin": None,
"Complete": False,
Expand Down
12 changes: 6 additions & 6 deletions src/bloomy/operations/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ class UserOperations(BaseOperations, UserOperationsMixin):
def details(
self,
user_id: int | None = None,
direct_reports: bool = False,
positions: bool = False,
include_direct_reports: bool = False,
include_positions: bool = False,
all: bool = False,
) -> UserDetails:
"""Retrieve details of a specific user.

Args:
user_id: The ID of the user (default: the current user ID)
direct_reports: Whether to include direct reports (default: False)
positions: Whether to include positions (default: False)
include_direct_reports: Whether to include direct reports (default: False)
include_positions: Whether to include positions (default: False)
all: Whether to include both direct reports and positions (default: False)

Returns:
Expand All @@ -39,10 +39,10 @@ def details(
direct_reports_data = None
positions_data = None

if direct_reports or all:
if include_direct_reports or all:
direct_reports_data = self.direct_reports(user_id)

if positions or all:
if include_positions or all:
positions_data = self.positions(user_id)

return self._transform_user_details(data, direct_reports_data, positions_data)
Expand Down
12 changes: 6 additions & 6 deletions tests/test_async_meetings.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,26 +343,26 @@ async def test_get_many_all_successful(
attendees_responses = {
100: [
{
"UserId": 456,
"Id": 456,
"Name": "John Doe",
"ImageUrl": "https://example.com/img1.jpg",
}
],
101: [
{
"UserId": 456,
"Id": 456,
"Name": "John Doe",
"ImageUrl": "https://example.com/img1.jpg",
},
{
"UserId": 789,
"Id": 789,
"Name": "Jane Smith",
"ImageUrl": "https://example.com/img2.jpg",
},
],
102: [
{
"UserId": 456,
"Id": 456,
"Name": "John Doe",
"ImageUrl": "https://example.com/img1.jpg",
}
Expand Down Expand Up @@ -431,7 +431,7 @@ def get_side_effect(url, **_kwargs):
elif "/200/attendees" in url:
mock_response.json.return_value = [
{
"UserId": 456,
"Id": 456,
"Name": "John Doe",
"ImageUrl": "https://example.com/img1.jpg",
}
Expand Down Expand Up @@ -522,7 +522,7 @@ async def delayed_get(*args, **_kwargs):
# Return attendees for any meeting
mock_response.json.return_value = [
{
"UserId": 456,
"Id": 456,
"Name": "John Doe",
"ImageUrl": "https://example.com/img.jpg",
}
Expand Down
Loading