Skip to content

Commit c867577

Browse files
authored
Merge pull request #1 from jpstroop/add-github-actions
Add GitHub actions
2 parents 03ac391 + fceef59 commit c867577

File tree

7 files changed

+228
-22
lines changed

7 files changed

+228
-22
lines changed

.github/workflows/ci.yml

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Check out repository
15+
uses: actions/checkout@v3
16+
17+
- name: Set up Python 3.13
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: "3.13"
21+
22+
- name: Install PDM
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install pdm
26+
27+
- name: Install dependencies
28+
run: |
29+
pdm install -G:all
30+
31+
- name: Run tests with coverage
32+
run: |
33+
pdm run pytest --cov-report=xml
34+
35+
- name: Upload coverage to Codecov
36+
uses: codecov/codecov-action@v3
37+
with:
38+
token: ${{ secrets.CODECOV_TOKEN }}
39+
file: ./coverage.xml
40+
fail_ci_if_error: false
41+
42+
format:
43+
runs-on: ubuntu-latest
44+
45+
steps:
46+
- name: Check out repository
47+
uses: actions/checkout@v3
48+
49+
- name: Set up Python 3.13
50+
uses: actions/setup-python@v4
51+
with:
52+
python-version: "3.13"
53+
54+
- name: Install PDM
55+
run: |
56+
python -m pip install --upgrade pip
57+
pip install pdm
58+
59+
- name: Install dependencies
60+
run: |
61+
pdm install -G:all
62+
63+
- name: Check Black formatting
64+
run: |
65+
pdm run black --check
66+
67+
- name: Check isort
68+
run: |
69+
pdm run isort --check
70+
71+
- name: Check unused imports with autoflake
72+
run: |
73+
pdm run autoflake
74+
75+
type-check:
76+
runs-on: ubuntu-latest
77+
78+
steps:
79+
- name: Check out repository
80+
uses: actions/checkout@v3
81+
82+
- name: Set up Python 3.13
83+
uses: actions/setup-python@v4
84+
with:
85+
python-version: "3.13"
86+
87+
- name: Install PDM
88+
run: |
89+
python -m pip install --upgrade pip
90+
pip install pdm
91+
92+
- name: Install dependencies
93+
run: |
94+
pdm install -G:all
95+
96+
- name: Run mypy
97+
run: |
98+
pdm run mypy
99+
100+
docs:
101+
runs-on: ubuntu-latest
102+
103+
steps:
104+
- name: Check out repository
105+
uses: actions/checkout@v3
106+
107+
- name: Set up Python 3.13
108+
uses: actions/setup-python@v4
109+
with:
110+
python-version: "3.13"
111+
112+
- name: Install PDM
113+
run: |
114+
python -m pip install --upgrade pip
115+
pip install pdm
116+
117+
- name: Install dependencies
118+
run: |
119+
pdm install -G:all
120+
121+
- name: Check markdown formatting
122+
run: |
123+
echo "Markdown format checking temporarily disabled"
124+
exit 0
125+
126+
# todo: https://github.com/ydah/mdformat-action
127+
128+
# build:
129+
# runs-on: ubuntu-latest
130+
# needs: [test, format, type-check, docs]
131+
132+
# steps:
133+
# - name: Check out repository
134+
# uses: actions/checkout@v3
135+
136+
# - name: Set up Python 3.13
137+
# uses: actions/setup-python@v4
138+
# with:
139+
# python-version: "3.13"
140+
141+
# - name: Install PDM
142+
# run: |
143+
# python -m pip install --upgrade pip
144+
# pip install pdm
145+
146+
# - name: Install dependencies
147+
# run: |
148+
# pdm install -G:all
149+
150+
# - name: Build package
151+
# run: |
152+
# pdm build

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# Python API Client for Fitbit™
22

3-
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
4-
[![PDM](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
3+
# Fitbit Client
4+
5+
[![CI](https://github.com/jpstroop/fitbit-client/actions/workflows/ci.yml/badge.svg)](https://github.com/jpstroop/fitbit-client/actions/workflows/ci.yml)
6+
[![codecov](https://codecov.io/gh/jpstroop/fitbit-client/branch/main/graph/badge.svg)](https://codecov.io/gh/jpstroop/fitbit-client)
57
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
6-
[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL%203.0-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
8+
[![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/release/python-3130/)
9+
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
710

811
A fully-typed Python client for interacting with the Fitbit API, featuring
912
OAuth2 PKCE authentication and resource-based API interactions.

TODO.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ if not food_id and not (food_name and calories):
3333
)
3434
```
3535

36+
- nutrition.py:
37+
- It doesn't seem like this should be passing tests when CALORIES is not an int:
38+
39+
```python
40+
# Handle both enum and string nutritional values
41+
for key, value in nutritional_values.items():
42+
if isinstance(key, NutritionalValue):
43+
params[key.value] = float(value)
44+
else:
45+
params[str(key)] = float(value)
46+
```
47+
48+
see: test_create_food_calories_from_fat_must_be_integer(nutrition_resource)
49+
3650
- exceptions.py
3751

3852
- Should ClientValidationException really subclass FitbitAPIException? It

codecov.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
codecov:
2+
require_ci_to_pass: yes
3+
strict_yaml_branch: main
4+
5+
coverage:
6+
precision: 2
7+
round: down
8+
range: "95...100"
9+
status:
10+
project:
11+
default:
12+
target: 100%
13+
threshold: 0%
14+
base: auto
15+
branches:
16+
- main
17+
if_not_found: failure
18+
if_ci_failed: error
19+
informational: false
20+
only_pulls: false
21+
patch:
22+
default:
23+
target: 100%
24+
threshold: 0%
25+
base: auto
26+
branches:
27+
- main
28+
if_not_found: failure
29+
if_ci_failed: error
30+
informational: false
31+
only_pulls: false
32+
33+
parsers:
34+
gcov:
35+
branch_detection:
36+
conditional: yes
37+
loop: yes
38+
method: no
39+
macro: no
40+
41+
comment:
42+
layout: "reach,diff,flags,files,footer"
43+
behavior: default
44+
require_changes: no

fitbit_client/resources/nutrition.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def create_food(
6161
calories: int,
6262
description: str,
6363
form_type: FoodFormType,
64-
nutritional_values: Dict[NutritionalValue, float],
64+
nutritional_values: Dict[NutritionalValue | str, float | int],
6565
user_id: str = "-",
6666
debug: bool = False,
6767
) -> JSONDict:
@@ -98,11 +98,10 @@ def create_food(
9898
and NutritionalValue.CALORIES_FROM_FAT in nutritional_values
9999
and not isinstance(nutritional_values[NutritionalValue.CALORIES_FROM_FAT], int)
100100
):
101-
raise ValidationException(
101+
raise ClientValidationException(
102102
message="Calories from fat must be an integer",
103-
status_code=400,
104-
error_type="validation",
105-
field_name="caloriesFromFat",
103+
error_type="client_validation",
104+
field_name="CALORIES_FROM_FAT",
106105
)
107106
# Handle both enum and string nutritional values
108107
for key, value in nutritional_values.items():

pyproject.toml

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ codeformatters = [
7373
extensions = [
7474
"gfm"
7575
]
76+
exclude = [".venv/**"]
7677

7778
[tool.pytest.ini_options]
7879
testpaths = ["tests"]
@@ -110,35 +111,28 @@ strict_optional = true
110111
warn_redundant_casts = true
111112
warn_unused_ignores = true
112113
warn_no_return = true
113-
# warn_unreachable = true
114+
warn_unreachable = true
114115
# disallow_any_generics = true
115116
disallow_subclassing_any = true
116117
disallow_untyped_calls = true
117-
118-
# Show details about error locations
119-
# show_column_numbers = true
118+
# output formatting
120119
show_error_codes = true
121120
pretty = true
122121
error_summary = true
123122
show_error_context = true
124-
# show_traceback = true
125-
126123

127-
# Necessary for most libraries
128124
[[tool.mypy.overrides]]
129125
module = [
130126
"requests.*",
131127
"requests_oauthlib.*",
132-
"pytest.*",
133-
"pyOpenSSL.*"
134128
]
135129
ignore_missing_imports = true
136130

137131

138132
[tool.pdm.scripts]
139133
autoflake = { cmd = "autoflake . -r --remove-unused-variables --remove-all-unused-imports --ignore-pass-after-docstring --exclude ./.venv/*,./_scripts/*" }
140134
headers = { cmd = "python lint/add_file_headers.py" }
141-
typecheck = { cmd = "mypy --show-error-codes --pretty --no-incremental fitbit_client" }
135+
mypy = { cmd = "mypy --pretty --no-incremental --warn-unused-configs fitbit_client" }
142136
black = { cmd = "black ." }
143137
isort = { cmd = "isort ." }
144138
mdformat = { cmd = "mdformat ." }

tests/resources/nutrition/test_create_food.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pytest import raises
77

88
# Local imports
9+
from fitbit_client.exceptions import ClientValidationException
910
from fitbit_client.exceptions import ValidationException
1011
from fitbit_client.resources.constants import FoodFormType
1112
from fitbit_client.resources.constants import NutritionalValue
@@ -82,7 +83,7 @@ def test_create_food_with_string_nutritional_values(nutrition_resource, mock_res
8283

8384
def test_create_food_calories_from_fat_must_be_integer(nutrition_resource):
8485
"""Test that calories_from_fat must be an integer"""
85-
with raises(ValidationException) as exc_info:
86+
with raises(ClientValidationException) as exc_info:
8687
nutrition_resource.create_food(
8788
name="Test Food",
8889
default_food_measurement_unit_id=147,
@@ -98,7 +99,6 @@ def test_create_food_calories_from_fat_must_be_integer(nutrition_resource):
9899
) # Float instead of integer
99100

100101
# Verify exception details
101-
assert exc_info.value.status_code == 400
102-
assert exc_info.value.error_type == "validation"
103-
assert exc_info.value.field_name == "caloriesFromFat"
102+
assert exc_info.value.error_type == "client_validation"
103+
assert exc_info.value.field_name == "CALORIES_FROM_FAT"
104104
assert "Calories from fat must be an integer" in str(exc_info.value)

0 commit comments

Comments
 (0)