Skip to content

Commit a8e86df

Browse files
committed
constantize sort direction and DRY up pagination validation
1 parent 9b81635 commit a8e86df

File tree

13 files changed

+484
-232
lines changed

13 files changed

+484
-232
lines changed

fitbit_client/exceptions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ def __init__(
165165
self.resource_name = resource_name
166166

167167

168+
class PaginationException(ClientValidationException):
169+
"""Raised when pagination-related parameters are invalid"""
170+
171+
def __init__(self, message: str, field_name: Optional[str] = None):
172+
"""Initialize pagination validation exception
173+
174+
Args:
175+
message: Error message describing the validation failure
176+
field_name: Optional name of the invalid field
177+
"""
178+
super().__init__(message=message, error_type="pagination", field_name=field_name)
179+
180+
168181
class IntradayValidationException(ClientValidationException):
169182
"""Raised when intraday request parameters are invalid"""
170183

fitbit_client/resources/activity.py

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
from fitbit_client.resources.base import BaseResource
1111
from fitbit_client.resources.constants import ActivityGoalPeriod
1212
from fitbit_client.resources.constants import ActivityGoalType
13-
from fitbit_client.utils.date_validation import validate_date_format
13+
from fitbit_client.resources.constants import SortDirection
1414
from fitbit_client.utils.date_validation import validate_date_param
15+
from fitbit_client.utils.pagination_validation import validate_pagination_params
1516

1617

1718
class ActivityResource(BaseResource):
@@ -136,11 +137,14 @@ def create_activity_log(
136137
"activities.json", params=params, user_id=user_id, http_method="POST", debug=debug
137138
)
138139

140+
@validate_date_param(field_name="before_date")
141+
@validate_date_param(field_name="after_date")
142+
@validate_pagination_params(max_limit=100)
139143
def get_activity_log_list(
140144
self,
141145
before_date: Optional[str] = None,
142146
after_date: Optional[str] = None,
143-
sort: str = "desc",
147+
sort: SortDirection = SortDirection.DESCENDING,
144148
limit: int = 100,
145149
offset: int = 0,
146150
user_id: str = "-",
@@ -154,42 +158,25 @@ def get_activity_log_list(
154158
Args:
155159
before_date: Get entries before this date (YYYY-MM-DD)
156160
after_date: Get entries after this date (YYYY-MM-DD)
157-
sort: Sort order ('asc' or 'desc')
161+
sort: Sort order - use 'asc' with after_date, 'desc' with before_date
158162
limit: Number of records to return (max 100)
159163
offset: Offset for pagination
160164
user_id: Optional user ID, defaults to current user
161165
debug: If True, a prints a curl command to stdout to help with debugging (default: False)
162166
167+
Note:
168+
Either before_date or after_date must be specified.
169+
The offset parameter only supports 0 and using other values may break your application.
170+
Use the pagination links in the response to iterate through results.
171+
163172
Returns:
164173
Dict containing activity logs matching the criteria
165174
166175
Raises:
167176
ValidationException: If limit exceeds 100 or sort order is invalid
168177
InvalidDateException: If date format is invalid
169178
"""
170-
if limit > 100:
171-
raise ValidationException(
172-
message="Maximum limit is 100 records",
173-
status_code=400,
174-
error_type="validation",
175-
field_name="limit",
176-
)
177-
178-
if sort not in ("asc", "desc"):
179-
raise ValidationException(
180-
message="Sort must be either 'asc' or 'desc'",
181-
status_code=400,
182-
error_type="validation",
183-
field_name="sort",
184-
)
185-
186-
# Validate dates if provided
187-
if before_date:
188-
validate_date_format(before_date, "before_date")
189-
if after_date:
190-
validate_date_format(after_date, "after_date")
191-
192-
params = {"sort": sort, "limit": limit, "offset": offset}
179+
params = {"sort": sort.value, "limit": limit, "offset": offset}
193180
if before_date:
194181
params["beforeDate"] = before_date
195182
if after_date:

fitbit_client/resources/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,10 @@ class IntradayDetailLevel(str, Enum):
257257
ONE_MINUTE = "1min"
258258
FIVE_MINUTES = "5min"
259259
FIFTEEN_MINUTES = "15min"
260+
261+
262+
class SortDirection(str, Enum):
263+
"""Sort directions for lists"""
264+
265+
ASCENDING = "asc"
266+
DESCENDING = "desc"

fitbit_client/resources/electrocardiogram.py

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
# Local imports
99
from fitbit_client.resources.base import BaseResource
10+
from fitbit_client.resources.constants import SortDirection
1011
from fitbit_client.utils.date_validation import validate_date_param
12+
from fitbit_client.utils.pagination_validation import validate_pagination_params
1113

1214

1315
class ElectrocardiogramResource(BaseResource):
@@ -23,11 +25,12 @@ class ElectrocardiogramResource(BaseResource):
2325

2426
@validate_date_param(field_name="before_date")
2527
@validate_date_param(field_name="after_date")
28+
@validate_pagination_params(max_limit=10)
2629
def get_ecg_log_list(
2730
self,
2831
before_date: Optional[str] = None,
2932
after_date: Optional[str] = None,
30-
sort: str = "desc",
33+
sort: SortDirection = SortDirection.DESCENDING,
3134
limit: int = 10,
3235
offset: int = 0,
3336
user_id: str = "-",
@@ -43,7 +46,7 @@ def get_ecg_log_list(
4346
only YYYY-MM-dd is required
4447
after_date: Return entries after this date (YYYY-MM-ddTHH:mm:ss),
4548
only YYYY-MM-dd is required
46-
sort: Sort order by date - use 'asc' with after_date, 'desc' with before_date
49+
sort: Sort order - use 'asc' with after_date, 'desc' with before_date
4750
limit: Number of entries to return (max 10)
4851
offset: Only 0 is supported
4952
user_id: Optional user ID, defaults to current user
@@ -59,32 +62,14 @@ def get_ecg_log_list(
5962
start time, average heart rate, waveform samples, result classification, etc.
6063
6164
Raises:
62-
ValueError: If neither before_date nor after_date is provided
63-
ValueError: If offset is not 0
64-
ValueError: If limit is greater than 10
65+
PaginatonError: If neither before_date nor after_date is specified
66+
PaginatonError: If offset is not 0
67+
PaginatonError: If limit exceeds 10
68+
PaginatonError: If sort is not 'asc' or 'desc'
69+
PaginatonError: If sort direction doesn't match date parameter
6570
InvalidDateException: If date format is invalid
6671
"""
67-
if not before_date and not after_date:
68-
raise ValueError("Either before_date or after_date must be specified")
69-
70-
if offset != 0:
71-
raise ValueError(
72-
"Only offset=0 is supported. To paginate, use the next and previous links in the response."
73-
)
74-
75-
if limit > 10:
76-
raise ValueError("Maximum limit is 10")
77-
78-
if sort not in ("asc", "desc"):
79-
raise ValueError("Sort must be either 'asc' or 'desc'")
80-
81-
# Validate sort direction matches date parameter
82-
if before_date and sort != "desc":
83-
raise ValueError("Must use sort='desc' with before_date")
84-
if after_date and sort != "asc":
85-
raise ValueError("Must use sort='asc' with after_date")
86-
87-
params = {"sort": sort, "limit": limit, "offset": offset}
72+
params = {"sort": sort.value, "limit": limit, "offset": offset}
8873

8974
if before_date:
9075
params["beforeDate"] = before_date

fitbit_client/resources/irregular_rhythm_notifications.py

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
# Local imports
99
from fitbit_client.resources.base import BaseResource
10+
from fitbit_client.resources.constants import SortDirection
1011
from fitbit_client.utils.date_validation import validate_date_param
12+
from fitbit_client.utils.pagination_validation import validate_pagination_params
1113

1214

1315
class IrregularRhythmNotificationsResource(BaseResource):
@@ -31,13 +33,14 @@ class IrregularRhythmNotificationsResource(BaseResource):
3133

3234
@validate_date_param(field_name="before_date")
3335
@validate_date_param(field_name="after_date")
36+
@validate_pagination_params(max_limit=10)
3437
def get_irn_alerts_list(
3538
self,
36-
sort: str,
37-
limit: int = 10,
38-
offset: int = 0,
3939
before_date: Optional[str] = None,
4040
after_date: Optional[str] = None,
41+
sort: SortDirection = SortDirection.DESCENDING,
42+
limit: int = 10,
43+
offset: int = 0,
4144
user_id: str = "-",
4245
debug: bool = False,
4346
) -> Dict[str, Any]:
@@ -47,49 +50,36 @@ def get_irn_alerts_list(
4750
API Reference: https://dev.fitbit.com/build/reference/web-api/irregular-rhythm-notifications/get-irn-alerts-list/
4851
4952
Args:
50-
sort: Sort order of entries by date. Use 'asc' with after_date, 'desc' with before_date
51-
limit: Number of entries to return (max 10)
52-
offset: Pagination offset (only 0 is supported)
5353
before_date: Date in yyyy-MM-ddTHH:mm:ss format (at least yyyy-MM-dd required)
5454
after_date: Date in yyyy-MM-ddTHH:mm:ss format (at least yyyy-MM-dd required)
55+
sort: Sort order - use 'asc' with after_date, 'desc' with before_date
56+
limit: Number of entries to return (max 10)
57+
offset: Pagination offset (only 0 is supported)
5558
user_id: The encoded ID of the user. Use "-" (dash) for current logged-in user.
5659
debug: If True, a prints a curl command to stdout to help with debugging (default: False)
5760
61+
Note:
62+
Either before_date or after_date must be specified.
63+
The offset parameter only supports 0 and using other values may break your application.
64+
Use the pagination links in the response to iterate through results.
65+
5866
Returns:
5967
Dictionary containing:
6068
- alerts: List of IRN alerts with detection times and heart rate data
6169
- pagination: Information for retrieving next/previous pages
6270
6371
Raises:
64-
ValueError: If neither before_date nor after_date is specified
65-
ValueError: If offset is not 0
66-
ValueError: If limit exceeds 10
67-
ValueError: If sort is not 'asc' or 'desc'
68-
ValueError: If sort direction doesn't match date parameter
72+
PaginatonError: If neither before_date nor after_date is specified
73+
PaginatonError: If offset is not 0
74+
PaginatonError: If limit exceeds 10
75+
PaginatonError: If sort is not 'asc' or 'desc'
76+
PaginatonError: If sort direction doesn't match date parameter
6977
InvalidDateException: If date format is invalid
7078
7179
Note:
7280
Either before_date or after_date must be specified.
7381
"""
74-
if offset != 0:
75-
raise ValueError("Only offset=0 is supported for IRN alerts pagination")
76-
77-
if limit > 10:
78-
raise ValueError("Maximum limit is 10 entries")
79-
80-
if not before_date and not after_date:
81-
raise ValueError("Either before_date or after_date must be specified")
82-
83-
if sort not in ["asc", "desc"]:
84-
raise ValueError("Sort must be either 'asc' or 'desc'")
85-
86-
if sort == "asc" and not after_date:
87-
raise ValueError("Must use after_date with ascending sort")
88-
89-
if sort == "desc" and not before_date:
90-
raise ValueError("Must use before_date with descending sort")
91-
92-
params = {"sort": sort, "limit": limit, "offset": offset}
82+
params = {"sort": sort.value, "limit": limit, "offset": offset}
9383

9484
if before_date:
9585
params["beforeDate"] = before_date

fitbit_client/resources/sleep.py

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
# Local imports
99
from fitbit_client.resources.base import BaseResource
10+
from fitbit_client.resources.constants import SortDirection
1011
from fitbit_client.utils.date_validation import validate_date_param
1112
from fitbit_client.utils.date_validation import validate_date_range_params
13+
from fitbit_client.utils.pagination_validation import validate_pagination_params
1214

1315

1416
class SleepResource(BaseResource):
@@ -212,11 +214,12 @@ def get_sleep_log_by_date_range(
212214

213215
@validate_date_param(field_name="before_date")
214216
@validate_date_param(field_name="after_date")
217+
@validate_pagination_params(max_limit=100)
215218
def get_sleep_log_list(
216219
self,
217220
before_date: Optional[str] = None,
218221
after_date: Optional[str] = None,
219-
sort: str = "desc",
222+
sort: SortDirection = SortDirection.DESCENDING,
220223
limit: int = 100,
221224
offset: int = 0,
222225
user_id: str = "-",
@@ -239,35 +242,21 @@ def get_sleep_log_list(
239242
Returns:
240243
Paginated list of sleep logs
241244
245+
Note:
246+
Either before_date or after_date must be specified.
247+
The offset parameter only supports 0 and using other values may break your application.
248+
Use the pagination links in the response to iterate through results.
249+
242250
Raises:
243-
ValueError: If neither before_date nor after_date is specified
244-
ValueError: If limit > 100
245-
ValueError: If sort is not 'asc' or 'desc'
246-
ValueError: If sort direction doesn't match date parameter
251+
PaginatonError: If neither before_date nor after_date is specified
252+
PaginatonError: If offset is not 0
253+
PaginatonError: If limit exceeds 10
254+
PaginatonError: If sort is not 'asc' or 'desc'
255+
PaginatonError: If sort direction doesn't match date parameter
247256
InvalidDateException: If date format is invalid
248257
249-
Note:
250-
- Either before_date or after_date must be specified
251-
- Use sort='desc' with before_date and sort='asc' with after_date
252-
- For pagination, use the next/previous links in the pagination response
253-
object rather than manually specifying offset
254258
"""
255-
if not before_date and not after_date:
256-
raise ValueError("Must specify either before_date or after_date")
257-
258-
if limit > 100:
259-
raise ValueError("Maximum limit is 100")
260-
261-
if sort not in ("asc", "desc"):
262-
raise ValueError("Sort must be either 'asc' or 'desc'")
263-
264-
# Validate sort direction matches date parameter
265-
if before_date and sort != "desc":
266-
raise ValueError("Must use sort='desc' with before_date")
267-
if after_date and sort != "asc":
268-
raise ValueError("Must use sort='asc' with after_date")
269-
270-
params = {"sort": sort, "limit": limit, "offset": offset}
259+
params = {"sort": sort.value, "limit": limit, "offset": offset}
271260
if before_date:
272261
params["beforeDate"] = before_date
273262
if after_date:

0 commit comments

Comments
 (0)