Skip to content

Commit a06fa4f

Browse files
authored
Merge pull request #9 from franccesco/fix/ci-tests-consistency
feat: add CI/CD pipelines and standardize API consistency
2 parents 12a3742 + 68bc590 commit a06fa4f

14 files changed

Lines changed: 214 additions & 76 deletions

.github/workflows/quality.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Code Quality
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
workflow_dispatch:
11+
12+
concurrency:
13+
group: quality-${{ github.ref }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
lint:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
- name: Setup Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: '3.12'
27+
28+
- name: Install uv
29+
uses: astral-sh/setup-uv@v5
30+
with:
31+
enable-cache: true
32+
33+
- name: Install dependencies
34+
run: uv sync --all-extras
35+
36+
- name: Check formatting
37+
run: uv run ruff format . --check
38+
39+
- name: Lint
40+
run: uv run ruff check .
41+
42+
typecheck:
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@v4
47+
48+
- name: Setup Python
49+
uses: actions/setup-python@v5
50+
with:
51+
python-version: '3.12'
52+
53+
- name: Install uv
54+
uses: astral-sh/setup-uv@v5
55+
with:
56+
enable-cache: true
57+
58+
- name: Install dependencies
59+
run: uv sync --all-extras
60+
61+
- name: Type check
62+
run: uv run pyright

.github/workflows/tests.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
workflow_dispatch:
11+
12+
concurrency:
13+
group: tests-${{ github.ref }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
test:
18+
runs-on: ubuntu-latest
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
python-version: ['3.12', '3.13']
23+
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
28+
- name: Setup Python ${{ matrix.python-version }}
29+
uses: actions/setup-python@v5
30+
with:
31+
python-version: ${{ matrix.python-version }}
32+
33+
- name: Install uv
34+
uses: astral-sh/setup-uv@v5
35+
with:
36+
enable-cache: true
37+
38+
- name: Install dependencies
39+
run: uv sync --all-extras
40+
41+
- name: Run tests with coverage
42+
run: uv run pytest --cov=bloomy --cov-report=term-missing --cov-report=xml
43+
44+
- name: Upload coverage report
45+
if: matrix.python-version == '3.12'
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: coverage-report
49+
path: coverage.xml

.pre-commit-config.yaml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22
# See https://pre-commit.com/hooks.html for more hooks
33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v3.2.0
5+
rev: v5.0.0
66
hooks:
77
- id: trailing-whitespace
88
- id: end-of-file-fixer
99
- id: check-yaml
1010
- id: check-added-large-files
11-
- repo: local
11+
12+
- repo: https://github.com/astral-sh/ruff-pre-commit
13+
rev: v0.8.3
1214
hooks:
13-
- id: standardrb
14-
name: standardrb
15-
description: Enforce the community Ruby Style Guide with standardrb
16-
entry: standardrb
17-
language: ruby
18-
types: ["ruby"]
19-
args: ["--fix"]
15+
- id: ruff
16+
args: [--fix]
17+
- id: ruff-format

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "bloomy-python"
3-
version = "0.18.0"
3+
version = "0.19.0"
44
description = "Python SDK for Bloom Growth API"
55
readme = "README.md"
66
authors = [{ name = "Franccesco Orozco", email = "franccesco@codingdose.info" }]
@@ -70,7 +70,7 @@ quote-style = "double"
7070
indent-style = "space"
7171

7272
[tool.pyright]
73-
include = ["src", "tests"]
73+
include = ["src"]
7474
pythonVersion = "3.12"
7575
typeCheckingMode = "strict"
7676
reportMissingImports = true

src/bloomy/operations/async_/meetings.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,13 @@ async def attendees(self, meeting_id: int) -> builtins.list[MeetingAttendee]:
8989

9090
# Map Id to UserId for compatibility
9191
return [
92-
MeetingAttendee.model_validate({
93-
"UserId": attendee["Id"],
94-
"Name": attendee["Name"],
95-
"ImageUrl": attendee["ImageUrl"]
96-
})
92+
MeetingAttendee.model_validate(
93+
{
94+
"UserId": attendee["Id"],
95+
"Name": attendee["Name"],
96+
"ImageUrl": attendee["ImageUrl"],
97+
}
98+
)
9799
for attendee in data
98100
]
99101

@@ -126,19 +128,21 @@ async def issues(
126128

127129
# Map meeting issue format to Issue model format
128130
return [
129-
Issue.model_validate({
130-
"Id": issue["Id"],
131-
"Name": issue["Name"],
132-
"DetailsUrl": issue.get("DetailsUrl"),
133-
"CreateDate": issue["CreateTime"],
134-
"MeetingId": issue["OriginId"],
135-
"MeetingName": issue["Origin"],
136-
"OwnerName": issue["Owner"]["Name"],
137-
"OwnerId": issue["Owner"]["Id"],
138-
"OwnerImageUrl": issue["Owner"]["ImageUrl"],
139-
"ClosedDate": issue.get("CloseTime"),
140-
"CompletionDate": issue.get("CompleteTime"),
141-
})
131+
Issue.model_validate(
132+
{
133+
"Id": issue["Id"],
134+
"Name": issue["Name"],
135+
"DetailsUrl": issue.get("DetailsUrl"),
136+
"CreateDate": issue["CreateTime"],
137+
"MeetingId": issue["OriginId"],
138+
"MeetingName": issue["Origin"],
139+
"OwnerName": issue["Owner"]["Name"],
140+
"OwnerId": issue["Owner"]["Id"],
141+
"OwnerImageUrl": issue["Owner"]["ImageUrl"],
142+
"ClosedDate": issue.get("CloseTime"),
143+
"CompletionDate": issue.get("CompleteTime"),
144+
}
145+
)
142146
for issue in data
143147
]
144148

src/bloomy/operations/async_/todos.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ async def create(
124124
payload["dueDate"] = due_date
125125

126126
if meeting_id is not None:
127-
# Meeting todo - use the correct endpoint
127+
# Meeting todo - use the correct endpoint with PascalCase keys
128128
payload = {
129129
"Title": title,
130130
"ForId": user_id,

src/bloomy/operations/todos.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def list(
6565
def create(
6666
self,
6767
title: str,
68-
meeting_id: int,
68+
meeting_id: int | None = None,
6969
due_date: str | None = None,
7070
user_id: int | None = None,
7171
notes: str | None = None,
@@ -74,7 +74,7 @@ def create(
7474
7575
Args:
7676
title: The title of the new todo
77-
meeting_id: The ID of the meeting associated with the todo
77+
meeting_id: The ID of the meeting associated with the todo (optional)
7878
due_date: The due date of the todo (optional)
7979
user_id: The ID of the user responsible for the todo
8080
(default: initialized user ID)
@@ -85,10 +85,15 @@ def create(
8585
8686
Example:
8787
```python
88+
# Create a user todo
8889
client.todo.create(
89-
title="New Todo", meeting_id=1, due_date="2024-06-15"
90+
title="New Todo", due_date="2024-06-15"
91+
)
92+
93+
# Create a meeting todo
94+
client.todo.create(
95+
title="Meeting Action", meeting_id=1, due_date="2024-06-15"
9096
)
91-
# Returns: Todo(id=1, name='New Todo', due_date='2024-06-15', ...)
9297
```
9398
9499
"""
@@ -98,13 +103,29 @@ def create(
98103
payload: dict[str, Any] = {
99104
"title": title,
100105
"accountableUserId": user_id,
101-
"notes": notes,
102106
}
103107

108+
if notes is not None:
109+
payload["notes"] = notes
110+
104111
if due_date is not None:
105112
payload["dueDate"] = due_date
106113

107-
response = self._client.post(f"L10/{meeting_id}/todos", json=payload)
114+
if meeting_id is not None:
115+
# Meeting todo - use the correct endpoint
116+
payload = {
117+
"Title": title,
118+
"ForId": user_id,
119+
}
120+
if notes is not None:
121+
payload["Notes"] = notes
122+
if due_date is not None:
123+
payload["dueDate"] = due_date
124+
response = self._client.post(f"L10/{meeting_id}/todos", json=payload)
125+
else:
126+
# User todo
127+
response = self._client.post("todo/create", json=payload)
128+
108129
response.raise_for_status()
109130
data = response.json()
110131

@@ -115,7 +136,7 @@ def create(
115136
"DetailsUrl": data.get("DetailsUrl"),
116137
"DueDate": data.get("DueDate"),
117138
"CompleteTime": None,
118-
"CreateTime": datetime.now().isoformat(),
139+
"CreateTime": data.get("CreateTime", datetime.now().isoformat()),
119140
"OriginId": meeting_id,
120141
"Origin": None,
121142
"Complete": False,

src/bloomy/operations/users.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ class UserOperations(BaseOperations, UserOperationsMixin):
1313
def details(
1414
self,
1515
user_id: int | None = None,
16-
direct_reports: bool = False,
17-
positions: bool = False,
16+
include_direct_reports: bool = False,
17+
include_positions: bool = False,
1818
all: bool = False,
1919
) -> UserDetails:
2020
"""Retrieve details of a specific user.
2121
2222
Args:
2323
user_id: The ID of the user (default: the current user ID)
24-
direct_reports: Whether to include direct reports (default: False)
25-
positions: Whether to include positions (default: False)
24+
include_direct_reports: Whether to include direct reports (default: False)
25+
include_positions: Whether to include positions (default: False)
2626
all: Whether to include both direct reports and positions (default: False)
2727
2828
Returns:
@@ -39,10 +39,10 @@ def details(
3939
direct_reports_data = None
4040
positions_data = None
4141

42-
if direct_reports or all:
42+
if include_direct_reports or all:
4343
direct_reports_data = self.direct_reports(user_id)
4444

45-
if positions or all:
45+
if include_positions or all:
4646
positions_data = self.positions(user_id)
4747

4848
return self._transform_user_details(data, direct_reports_data, positions_data)

tests/test_async_meetings.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -343,26 +343,26 @@ async def test_get_many_all_successful(
343343
attendees_responses = {
344344
100: [
345345
{
346-
"UserId": 456,
346+
"Id": 456,
347347
"Name": "John Doe",
348348
"ImageUrl": "https://example.com/img1.jpg",
349349
}
350350
],
351351
101: [
352352
{
353-
"UserId": 456,
353+
"Id": 456,
354354
"Name": "John Doe",
355355
"ImageUrl": "https://example.com/img1.jpg",
356356
},
357357
{
358-
"UserId": 789,
358+
"Id": 789,
359359
"Name": "Jane Smith",
360360
"ImageUrl": "https://example.com/img2.jpg",
361361
},
362362
],
363363
102: [
364364
{
365-
"UserId": 456,
365+
"Id": 456,
366366
"Name": "John Doe",
367367
"ImageUrl": "https://example.com/img1.jpg",
368368
}
@@ -431,7 +431,7 @@ def get_side_effect(url, **_kwargs):
431431
elif "/200/attendees" in url:
432432
mock_response.json.return_value = [
433433
{
434-
"UserId": 456,
434+
"Id": 456,
435435
"Name": "John Doe",
436436
"ImageUrl": "https://example.com/img1.jpg",
437437
}
@@ -522,7 +522,7 @@ async def delayed_get(*args, **_kwargs):
522522
# Return attendees for any meeting
523523
mock_response.json.return_value = [
524524
{
525-
"UserId": 456,
525+
"Id": 456,
526526
"Name": "John Doe",
527527
"ImageUrl": "https://example.com/img.jpg",
528528
}

0 commit comments

Comments
 (0)