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

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v3

- name: Set up Python 3.13
uses: actions/setup-python@v4
with:
python-version: "3.13"

- name: Install PDM
run: |
python -m pip install --upgrade pip
pip install pdm

- name: Install dependencies
run: |
pdm install -G:all

- name: Run tests with coverage
run: |
pdm run pytest --cov-report=xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
fail_ci_if_error: false

format:
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v3

- name: Set up Python 3.13
uses: actions/setup-python@v4
with:
python-version: "3.13"

- name: Install PDM
run: |
python -m pip install --upgrade pip
pip install pdm

- name: Install dependencies
run: |
pdm install -G:all

- name: Check Black formatting
run: |
pdm run black --check

- name: Check isort
run: |
pdm run isort --check

- name: Check unused imports with autoflake
run: |
pdm run autoflake

type-check:
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v3

- name: Set up Python 3.13
uses: actions/setup-python@v4
with:
python-version: "3.13"

- name: Install PDM
run: |
python -m pip install --upgrade pip
pip install pdm

- name: Install dependencies
run: |
pdm install -G:all

- name: Run mypy
run: |
pdm run mypy

docs:
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v3

- name: Set up Python 3.13
uses: actions/setup-python@v4
with:
python-version: "3.13"

- name: Install PDM
run: |
python -m pip install --upgrade pip
pip install pdm

- name: Install dependencies
run: |
pdm install -G:all

- name: Check markdown formatting
run: |
echo "Markdown format checking temporarily disabled"
exit 0

# todo: https://github.com/ydah/mdformat-action

# build:
# runs-on: ubuntu-latest
# needs: [test, format, type-check, docs]

# steps:
# - name: Check out repository
# uses: actions/checkout@v3

# - name: Set up Python 3.13
# uses: actions/setup-python@v4
# with:
# python-version: "3.13"

# - name: Install PDM
# run: |
# python -m pip install --upgrade pip
# pip install pdm

# - name: Install dependencies
# run: |
# pdm install -G:all

# - name: Build package
# run: |
# pdm build
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Python API Client for Fitbit™

[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![PDM](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
# Fitbit Client

[![CI](https://github.com/jpstroop/fitbit-client/actions/workflows/ci.yml/badge.svg)](https://github.com/jpstroop/fitbit-client/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/jpstroop/fitbit-client/branch/main/graph/badge.svg)](https://codecov.io/gh/jpstroop/fitbit-client)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL%203.0-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/release/python-3130/)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)

A fully-typed Python client for interacting with the Fitbit API, featuring
OAuth2 PKCE authentication and resource-based API interactions.
Expand Down
14 changes: 14 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ if not food_id and not (food_name and calories):
)
```

- nutrition.py:
- It doesn't seem like this should be passing tests when CALORIES is not an int:

```python
# Handle both enum and string nutritional values
for key, value in nutritional_values.items():
if isinstance(key, NutritionalValue):
params[key.value] = float(value)
else:
params[str(key)] = float(value)
```

see: test_create_food_calories_from_fat_must_be_integer(nutrition_resource)

- exceptions.py

- Should ClientValidationException really subclass FitbitAPIException? It
Expand Down
44 changes: 44 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
codecov:
require_ci_to_pass: yes
strict_yaml_branch: main

coverage:
precision: 2
round: down
range: "95...100"
status:
project:
default:
target: 100%
threshold: 0%
base: auto
branches:
- main
if_not_found: failure
if_ci_failed: error
informational: false
only_pulls: false
patch:
default:
target: 100%
threshold: 0%
base: auto
branches:
- main
if_not_found: failure
if_ci_failed: error
informational: false
only_pulls: false

parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no

comment:
layout: "reach,diff,flags,files,footer"
behavior: default
require_changes: no
9 changes: 4 additions & 5 deletions fitbit_client/resources/nutrition.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def create_food(
calories: int,
description: str,
form_type: FoodFormType,
nutritional_values: Dict[NutritionalValue, float],
nutritional_values: Dict[NutritionalValue | str, float | int],
user_id: str = "-",
debug: bool = False,
) -> JSONDict:
Expand Down Expand Up @@ -98,11 +98,10 @@ def create_food(
and NutritionalValue.CALORIES_FROM_FAT in nutritional_values
and not isinstance(nutritional_values[NutritionalValue.CALORIES_FROM_FAT], int)
):
raise ValidationException(
raise ClientValidationException(
message="Calories from fat must be an integer",
status_code=400,
error_type="validation",
field_name="caloriesFromFat",
error_type="client_validation",
field_name="CALORIES_FROM_FAT",
)
# Handle both enum and string nutritional values
for key, value in nutritional_values.items():
Expand Down
14 changes: 4 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ codeformatters = [
extensions = [
"gfm"
]
exclude = [".venv/**"]

[tool.pytest.ini_options]
testpaths = ["tests"]
Expand Down Expand Up @@ -110,35 +111,28 @@ strict_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
# warn_unreachable = true
warn_unreachable = true
# disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true

# Show details about error locations
# show_column_numbers = true
# output formatting
show_error_codes = true
pretty = true
error_summary = true
show_error_context = true
# show_traceback = true


# Necessary for most libraries
[[tool.mypy.overrides]]
module = [
"requests.*",
"requests_oauthlib.*",
"pytest.*",
"pyOpenSSL.*"
]
ignore_missing_imports = true


[tool.pdm.scripts]
autoflake = { cmd = "autoflake . -r --remove-unused-variables --remove-all-unused-imports --ignore-pass-after-docstring --exclude ./.venv/*,./_scripts/*" }
headers = { cmd = "python lint/add_file_headers.py" }
typecheck = { cmd = "mypy --show-error-codes --pretty --no-incremental fitbit_client" }
mypy = { cmd = "mypy --pretty --no-incremental --warn-unused-configs fitbit_client" }
black = { cmd = "black ." }
isort = { cmd = "isort ." }
mdformat = { cmd = "mdformat ." }
Expand Down
8 changes: 4 additions & 4 deletions tests/resources/nutrition/test_create_food.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pytest import raises

# Local imports
from fitbit_client.exceptions import ClientValidationException
from fitbit_client.exceptions import ValidationException
from fitbit_client.resources.constants import FoodFormType
from fitbit_client.resources.constants import NutritionalValue
Expand Down Expand Up @@ -82,7 +83,7 @@ def test_create_food_with_string_nutritional_values(nutrition_resource, mock_res

def test_create_food_calories_from_fat_must_be_integer(nutrition_resource):
"""Test that calories_from_fat must be an integer"""
with raises(ValidationException) as exc_info:
with raises(ClientValidationException) as exc_info:
nutrition_resource.create_food(
name="Test Food",
default_food_measurement_unit_id=147,
Expand All @@ -98,7 +99,6 @@ def test_create_food_calories_from_fat_must_be_integer(nutrition_resource):
) # Float instead of integer

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