diff --git a/examples/auth_examples.py b/examples/auth_examples.py new file mode 100644 index 0000000..3c1ed5f --- /dev/null +++ b/examples/auth_examples.py @@ -0,0 +1,76 @@ +import os +from getpass import getpass + +from otf_api import Otf, OtfAuth, OtfAuthConfig, OtfUser + +USERNAME = os.getenv("OTF_EMAIL") or input("Enter your OTF email: ") +PASSWORD = os.getenv("OTF_PASSWORD") or getpass("Enter your OTF password: ") + + +def main(): + """ + OtfAuthConfig provides three options currently: + - cache_device_data: bool - Whether to register/cache device data with AWS Cognito + I'm not sure if there is any benefit to this at this point, but in general this is used to allow skipping MFA + - cache_tokens_plaintext: bool - Whether to cache the tokens in plaintext in the config file - this is an obvious + security risk, but it's useful for development purposes. If you want to do this, it is at your own risk. The + benefit is that after you log in with your username/password once, you can use the cached tokens to log in + without providing them again. + """ + + # This is the most configurable way to access the API but also the most verbose + + auth_config = OtfAuthConfig(cache_device_data=True, cache_tokens_plaintext=True) + + auth = OtfAuth.create(USERNAME, PASSWORD, config=auth_config) + user = OtfUser(auth=auth) + print(user.otf_auth.auth_type) + + # You can also use `login` to log in with a username and password, with the config being optional + user = OtfUser.login(USERNAME, PASSWORD, config=auth_config) + print(user.otf_auth.auth_type) + + # If you have tokens available you can provide them using `from_tokens` instead of `login` + + user = OtfUser.from_tokens( + user.cognito.access_token, user.cognito.id_token, user.cognito.refresh_token, config=auth_config + ) + print(user.otf_auth.auth_type) + + # Likewise, if you have a Cognito instance you can provide that as well + + user = OtfUser.from_cognito(cognito=user.cognito, config=auth_config) + print(user.otf_auth.auth_type) + + # If you have already logged in and you cached your tokens, you can use `from_cache` to create a user object + # without providing any tokens + + user = OtfUser.from_cache(config=auth_config) + print(user.otf_auth.auth_type) + + # if you want to clear the cached tokens, you can use `clear_cached_tokens` + # if you want to clear the cached device data, you can use `clear_cached_device_data` + # if you want to clear both, you can use `clear_cache` + user.clear_cached_tokens() + user.clear_cached_device_data() + user.clear_cache() + + # now if we tried to log in from cache, it would raise a ValueError + try: + user = OtfUser.from_cache(config=auth_config) + except ValueError as e: + print(e) + + # to instantiate an Otf instance, you can provide either a user object or an auth object + + # if you provide a user then authenticate doesn't need to be called + otf_from_user = Otf(user=user) + print(otf_from_user.member_uuid) + + # whereas if you provide an auth object, authenticate will be called + otf_from_auth = Otf(auth=auth) + print(otf_from_auth.member_uuid) + + +if __name__ == "__main__": + main() diff --git a/examples/challenge_tracker_examples.py b/examples/challenge_tracker_examples.py index 73105b2..bf7eb0b 100644 --- a/examples/challenge_tracker_examples.py +++ b/examples/challenge_tracker_examples.py @@ -1,7 +1,7 @@ import os from getpass import getpass -from otf_api import Otf +from otf_api import Otf, OtfUser from otf_api.models import ChallengeType, EquipmentType USERNAME = os.getenv("OTF_EMAIL") or input("Enter your OTF email: ") @@ -9,34 +9,17 @@ def main(): - with Otf(USERNAME, PASSWORD) as otf: - # challenge tracker content is an overview of the challenges OTF runs - # and your participation in them - challenge_tracker_content = otf.get_challenge_tracker_content() - print(challenge_tracker_content.benchmarks[0].model_dump_json(indent=4)) + user = OtfUser.login(USERNAME, PASSWORD) + otf = Otf(user=user) + # challenge tracker content is an overview of the challenges OTF runs + # and your participation in them + challenge_tracker_content = otf.get_challenge_tracker_content() + print(challenge_tracker_content.benchmarks[0].model_dump_json(indent=4)) - """ - { - "equipment_id": 2, - "equipment_name": "Treadmill", - "years": [ - { - "year": "2024", - "is_participated": false, - "in_progress": false - }, - ... - ], - "logo_url": "https://otf-icons.s3.amazonaws.com/benchmarks/Treadmill.png" - } - """ - - print(challenge_tracker_content.challenges[0].model_dump_json(indent=4)) - """ - { - "challenge_category_id": 10, - "challenge_sub_category_id": 8, - "challenge_name": "Catch Me If You Can 3G", + """ + { + "equipment_id": 2, + "equipment_name": "Treadmill", "years": [ { "year": "2024", @@ -45,67 +28,85 @@ def main(): }, ... ], - "logo_url": "https://otf-icons.s3.amazonaws.com/challenges/CatchMeIfYouCan.png" - } - """ - - # challenge tracker details are detailed information about specific challenges - # this endpoint takes an equipment type and a challenge type as arguments - tread_challenge_details = otf.get_challenge_tracker_detail(EquipmentType.Treadmill, ChallengeType.Other) - print(tread_challenge_details.details[0].model_dump_json(indent=4)) + "logo_url": "https://otf-icons.s3.amazonaws.com/benchmarks/Treadmill.png" + } + """ - """ + print(challenge_tracker_content.challenges[0].model_dump_json(indent=4)) + """ + { + "challenge_category_id": 10, + "challenge_sub_category_id": 8, + "challenge_name": "Catch Me If You Can 3G", + "years": [ { - "challenge_category_id": 10, - "challenge_sub_category_id": null, + "year": "2024", + "is_participated": false, + "in_progress": false + }, + ... + ], + "logo_url": "https://otf-icons.s3.amazonaws.com/challenges/CatchMeIfYouCan.png" + } + """ + + # challenge tracker details are detailed information about specific challenges + # this endpoint takes an equipment type and a challenge type as arguments + tread_challenge_details = otf.get_challenge_tracker_detail(EquipmentType.Treadmill, ChallengeType.Other) + print(tread_challenge_details.details[0].model_dump_json(indent=4)) + + """ + { + "challenge_category_id": 10, + "challenge_sub_category_id": null, + "equipment_id": 2, + "equipment_name": "Treadmill", + "metric_entry": { + "title": "22 MIN", "equipment_id": 2, - "equipment_name": "Treadmill", - "metric_entry": { - "title": "22 MIN", - "equipment_id": 2, - "entry_type": "Distance", - "metric_key": "22MIN", - "min_value": "0.16093440000000003", - "max_value": "8.04672" - }, - "challenge_name": "Catch me If You Can", - "logo_url": "https://otf-icons.s3.amazonaws.com/challenges/CatchMeIfYouCan.png", - "best_record": 1.40012928, - "last_record": 1.40012928, - "previous_record": 1.40012928, - "unit": "km", - "goals": null, - "challenge_histories": [ - { - "challenge_objective": "None", - "challenge_id": 449906, - "studio_id": 1267, - "studio_name": "AnyTown OH - East", - "start_date": "2024-02-06 00:00:00", - "end_date": "2024-02-06 23:59:00", - "total_result": 1.40012928, - "is_finished": true, - "benchmark_histories": [ - { - "studio_name": "AnyTown OH - East", - "equipment_id": 2, - "result": 1.40012928, - "date_created": "2024-02-06 16:01:26", - "date_updated": "2024-02-06 16:01:26", - "class_time": "2024-02-06 09:45:00", - "challenge_sub_category_id": null, - "class_id": 86842386, - "substitute_id": 1, - "weight_lbs": 0, - "workout_type_id": null, - "workout_id": null, - "linked_challenges": [] - } - ] - } - ] - } - """ + "entry_type": "Distance", + "metric_key": "22MIN", + "min_value": "0.16093440000000003", + "max_value": "8.04672" + }, + "challenge_name": "Catch me If You Can", + "logo_url": "https://otf-icons.s3.amazonaws.com/challenges/CatchMeIfYouCan.png", + "best_record": 1.40012928, + "last_record": 1.40012928, + "previous_record": 1.40012928, + "unit": "km", + "goals": null, + "challenge_histories": [ + { + "challenge_objective": "None", + "challenge_id": 449906, + "studio_id": 1267, + "studio_name": "AnyTown OH - East", + "start_date": "2024-02-06 00:00:00", + "end_date": "2024-02-06 23:59:00", + "total_result": 1.40012928, + "is_finished": true, + "benchmark_histories": [ + { + "studio_name": "AnyTown OH - East", + "equipment_id": 2, + "result": 1.40012928, + "date_created": "2024-02-06 16:01:26", + "date_updated": "2024-02-06 16:01:26", + "class_time": "2024-02-06 09:45:00", + "challenge_sub_category_id": null, + "class_id": 86842386, + "substitute_id": 1, + "weight_lbs": 0, + "workout_type_id": null, + "workout_id": null, + "linked_challenges": [] + } + ] + } + ] + } + """ if __name__ == "__main__": diff --git a/examples/class_bookings_examples.py b/examples/class_bookings_examples.py index ad0a22a..6ccd01c 100644 --- a/examples/class_bookings_examples.py +++ b/examples/class_bookings_examples.py @@ -2,127 +2,127 @@ from datetime import datetime, time from getpass import getpass -from otf_api import Otf -from otf_api.filters import ClassFilter -from otf_api.models import ClassType -from otf_api.models.classes import DoW +from otf_api import Otf, OtfUser +from otf_api.filters import ClassFilter, ClassType, DoW USERNAME = os.getenv("OTF_EMAIL") or input("Enter your OTF email: ") PASSWORD = os.getenv("OTF_PASSWORD") or getpass("Enter your OTF password: ") def main(): - with Otf(USERNAME, PASSWORD) as otf: - resp = otf.get_bookings(start_date=datetime.today().date()) - print(resp.model_dump_json(indent=4)) - - studios = otf.search_studios_by_geo(40.7831, 73.9712, distance=100) - - studio_uuids = [studio.studio_uuid for studio in studios.studios] - - cf = ClassFilter( - day_of_week=[DoW.TUESDAY, DoW.THURSDAY], - start_time=time(9, 45), - class_type=ClassType.get_standard_class_types(), - ) - cf2 = ClassFilter(day_of_week=DoW.SATURDAY, start_time=time(10, 30), class_type=ClassType.TREAD_50) - - classes = otf.get_classes(studio_uuids=studio_uuids, filters=[cf, cf2]) - - print(classes.classes[0].model_dump_json(indent=4)) - - """ - { - "id": "0e39ef70-7403-49c1-8605-4a72643bd201", - "ot_base_class_uuid": "08cebfdb-e127-48d4-8a7f-e6ea4dd85c18", - "starts_at": "2024-06-13 10:00:00+00:00", - "starts_at_local": "2024-06-13 05:00:00", - "ends_at": "2024-06-13 11:00:00+00:00", - "ends_at_local": "2024-06-13 06:00:00", - "name": "Orange 3G", - "type": "ORANGE_60", - "studio": ..., - "coach": ..., - "max_capacity": 36, - "booking_capacity": 36, - "waitlist_size": 0, - "full": false, - "waitlist_available": false, - "canceled": false, - "mbo_class_id": "30809", - "mbo_class_schedule_id": "2655", - "mbo_class_description_id": "102", - "created_at": "2024-05-14 10:33:32.406000+00:00", - "updated_at": "2024-06-13 01:58:55.233000+00:00" - } - """ - - # You can also get the classes that you have booked - # You can pass a start_date, end_date, status, and limit as arguments - - bookings = otf.get_bookings() - - print("Latest Upcoming Class:") - print(bookings.bookings[-1].model_dump_json(indent=4)) - - """ - { - "class_booking_id": 870700285, - "class_booking_uuid": "a36d76b1-0a55-4143-b96b-646e7520ca39", - "studio_id": 1234, - "class_id": 376344282, - "is_intro": false, - "member_id": 234488148, - "status": "Booked", - "booked_date": "2024-09-10T04:26:11Z", - "checked_in_date": null, - "cancelled_date": null, - "created_date": "2024-09-10T04:26:11Z", - "updated_date": "2024-09-10T04:26:13Z", - "is_deleted": false, - "waitlist_position": null, - "otf_class": { - "starts_at_local": "2024-09-28T10:30:00", - "ends_at_local": "2024-09-28T11:20:00", - "name": "Tread 50", - "class_uuid": "82ec9b55-950a-484f-818f-cd2344ce83fd", - "is_available": true, - "is_cancelled": false, - "program_name": "Group Fitness", - "coach_id": 1204786, - "studio": { - "studio_uuid": "49e360d1-f8ef-4091-a23f-61b321cb283c", - "studio_name": "AnyTown OH - East", - "description": "", - "status": "Active", - "time_zone": "America/Chicago", - "studio_id": 1267, - "allows_cr_waitlist": true - }, - "coach": { - "coach_uuid": "973516a8-0c6b-41ec-916c-1da9913b9a16", - "name": "Friendly", - "first_name": "Friendly", - "last_name": "Coach" - }, - "location": { - "address_one": "123 S Main St", - "address_two": null, - "city": "AnyTown", - "country": null, - "distance": null, - "latitude": 91.73407745, - "location_name": null, - "longitude": -80.92264626, - "phone_number": "2042348963", - "postal_code": "11111", - "state": "Ohio" - }, - "virtual_class": null + user = OtfUser.login(USERNAME, PASSWORD) + otf = Otf(user=user) + + resp = otf.get_bookings(start_date=datetime.today().date()) + print(resp.model_dump_json(indent=4)) + + studios = otf.search_studios_by_geo(40.7831, 73.9712, distance=100) + + studio_uuids = [studio.studio_uuid for studio in studios.studios] + + cf = ClassFilter( + day_of_week=[DoW.TUESDAY, DoW.THURSDAY], + start_time=time(9, 45), + class_type=ClassType.get_standard_class_types(), + ) + cf2 = ClassFilter(day_of_week=DoW.SATURDAY, start_time=time(10, 30), class_type=ClassType.TREAD_50) + + classes = otf.get_classes(studio_uuids=studio_uuids, filters=[cf, cf2]) + + print(classes.classes[0].model_dump_json(indent=4)) + + """ + { + "id": "0e39ef70-7403-49c1-8605-4a72643bd201", + "ot_base_class_uuid": "08cebfdb-e127-48d4-8a7f-e6ea4dd85c18", + "starts_at": "2024-06-13 10:00:00+00:00", + "starts_at_local": "2024-06-13 05:00:00", + "ends_at": "2024-06-13 11:00:00+00:00", + "ends_at_local": "2024-06-13 06:00:00", + "name": "Orange 3G", + "type": "ORANGE_60", + "studio": ..., + "coach": ..., + "max_capacity": 36, + "booking_capacity": 36, + "waitlist_size": 0, + "full": false, + "waitlist_available": false, + "canceled": false, + "mbo_class_id": "30809", + "mbo_class_schedule_id": "2655", + "mbo_class_description_id": "102", + "created_at": "2024-05-14 10:33:32.406000+00:00", + "updated_at": "2024-06-13 01:58:55.233000+00:00" + } + """ + + # You can also get the classes that you have booked + # You can pass a start_date, end_date, status, and limit as arguments + + bookings = otf.get_bookings() + + print("Latest Upcoming Class:") + print(bookings.bookings[-1].model_dump_json(indent=4)) + + """ + { + "class_booking_id": 870700285, + "class_booking_uuid": "a36d76b1-0a55-4143-b96b-646e7520ca39", + "studio_id": 1234, + "class_id": 376344282, + "is_intro": false, + "member_id": 234488148, + "status": "Booked", + "booked_date": "2024-09-10T04:26:11Z", + "checked_in_date": null, + "cancelled_date": null, + "created_date": "2024-09-10T04:26:11Z", + "updated_date": "2024-09-10T04:26:13Z", + "is_deleted": false, + "waitlist_position": null, + "otf_class": { + "starts_at_local": "2024-09-28T10:30:00", + "ends_at_local": "2024-09-28T11:20:00", + "name": "Tread 50", + "class_uuid": "82ec9b55-950a-484f-818f-cd2344ce83fd", + "is_available": true, + "is_cancelled": false, + "program_name": "Group Fitness", + "coach_id": 1204786, + "studio": { + "studio_uuid": "49e360d1-f8ef-4091-a23f-61b321cb283c", + "studio_name": "AnyTown OH - East", + "description": "", + "status": "Active", + "time_zone": "America/Chicago", + "studio_id": 1267, + "allows_cr_waitlist": true }, - "is_home_studio": true - } - """ + "coach": { + "coach_uuid": "973516a8-0c6b-41ec-916c-1da9913b9a16", + "name": "Friendly", + "first_name": "Friendly", + "last_name": "Coach" + }, + "location": { + "address_one": "123 S Main St", + "address_two": null, + "city": "AnyTown", + "country": null, + "distance": null, + "latitude": 91.73407745, + "location_name": null, + "longitude": -80.92264626, + "phone_number": "2042348963", + "postal_code": "11111", + "state": "Ohio" + }, + "virtual_class": null + }, + "is_home_studio": true + } + """ if __name__ == "__main__": diff --git a/examples/studio_examples.py b/examples/studio_examples.py index 3ed6457..09f65a9 100644 --- a/examples/studio_examples.py +++ b/examples/studio_examples.py @@ -1,72 +1,74 @@ import os from getpass import getpass -from otf_api import Otf +from otf_api import Otf, OtfUser USERNAME = os.getenv("OTF_EMAIL") or input("Enter your OTF email: ") PASSWORD = os.getenv("OTF_PASSWORD") or getpass("Enter your OTF password: ") def main(): - with Otf(USERNAME, PASSWORD) as otf: - # if you need to figure out what studios are in an area, you can call `search_studios_by_geo` - # which takes latitude, longitude, distance, page_index, and page_size as arguments - # but you'll generally just need the first 3 - # same as with classes, you can leave it blank and get the studios within 50 miles of your home studio - studios_by_geo = otf.search_studios_by_geo() - print(studios_by_geo.studios[0].model_dump_json(indent=4)) + user = OtfUser.login(USERNAME, PASSWORD) + otf = Otf(user=user) - """ - { - "studio_id": 1297, - "studio_uuid": "8645fb2b-ef66-4d9d-bda1-f508091ec891", - "mbo_studio_id": 8612481, - "studio_number": "05414", - "studio_name": "AnyTown OH - East", - "studio_physical_location_id": 494, - "time_zone": "America/Chicago", - "contact_email": "studiomanager05414@orangetheoryfitness.com", - "studio_token": "ec2459b2-32b5-4b7e-9759-55270626925a", - "environment": "PROD", - "pricing_level": "", - "tax_rate": "0.000000", - "accepts_visa_master_card": true, - "accepts_american_express": true, - "accepts_discover": true, - "accepts_ach": false, - "is_integrated": true, - "description": "", - "studio_version": "", - "studio_status": "Active", - "open_date": "2017-01-13 00:00:00", - "re_open_date": "2020-05-26 00:00:00", - "studio_type_id": 2, - "sms_package_enabled": false, - "allows_dashboard_access": false, - "allows_cr_waitlist": true, - "cr_waitlist_flag_last_updated": "2020-07-09 02:43:55+00:00", - "royalty_rate": 0, - "marketing_fund_rate": 0, - "commission_percent": 0, - "is_mobile": null, - "is_otbeat": null, - "distance": 0.0, - "studio_location": ..., - "studio_location_localized": ..., - "studio_profiles": { - "is_web": true, - "intro_capacity": 1, - "is_crm": true - }, - "social_media_links": ... - } - """ + # if you need to figure out what studios are in an area, you can call `search_studios_by_geo` + # which takes latitude, longitude, distance, page_index, and page_size as arguments + # but you'll generally just need the first 3 + # same as with classes, you can leave it blank and get the studios within 50 miles of your home studio + studios_by_geo = otf.search_studios_by_geo() + print(studios_by_geo.studios[0].model_dump_json(indent=4)) - # if you need to get detailed information about a studio, you can call `get_studio_detail` - # which takes a studio_uuid as an argument, but you can leave it blank to get details about your home studio - # this one has a result structure very much like the previous one - studio_detail = otf.get_studio_detail() - print(studio_detail.model_dump_json(indent=4)) + """ + { + "studio_id": 1297, + "studio_uuid": "8645fb2b-ef66-4d9d-bda1-f508091ec891", + "mbo_studio_id": 8612481, + "studio_number": "05414", + "studio_name": "AnyTown OH - East", + "studio_physical_location_id": 494, + "time_zone": "America/Chicago", + "contact_email": "studiomanager05414@orangetheoryfitness.com", + "studio_token": "ec2459b2-32b5-4b7e-9759-55270626925a", + "environment": "PROD", + "pricing_level": "", + "tax_rate": "0.000000", + "accepts_visa_master_card": true, + "accepts_american_express": true, + "accepts_discover": true, + "accepts_ach": false, + "is_integrated": true, + "description": "", + "studio_version": "", + "studio_status": "Active", + "open_date": "2017-01-13 00:00:00", + "re_open_date": "2020-05-26 00:00:00", + "studio_type_id": 2, + "sms_package_enabled": false, + "allows_dashboard_access": false, + "allows_cr_waitlist": true, + "cr_waitlist_flag_last_updated": "2020-07-09 02:43:55+00:00", + "royalty_rate": 0, + "marketing_fund_rate": 0, + "commission_percent": 0, + "is_mobile": null, + "is_otbeat": null, + "distance": 0.0, + "studio_location": ..., + "studio_location_localized": ..., + "studio_profiles": { + "is_web": true, + "intro_capacity": 1, + "is_crm": true + }, + "social_media_links": ... + } + """ + + # if you need to get detailed information about a studio, you can call `get_studio_detail` + # which takes a studio_uuid as an argument, but you can leave it blank to get details about your home studio + # this one has a result structure very much like the previous one + studio_detail = otf.get_studio_detail() + print(studio_detail.model_dump_json(indent=4)) if __name__ == "__main__": diff --git a/examples/workout_examples.py b/examples/workout_examples.py index 6587ca6..2868a85 100644 --- a/examples/workout_examples.py +++ b/examples/workout_examples.py @@ -1,190 +1,192 @@ import os from getpass import getpass -from otf_api import Otf +from otf_api import Otf, OtfUser USERNAME = os.getenv("OTF_EMAIL") or input("Enter your OTF email: ") PASSWORD = os.getenv("OTF_PASSWORD") or getpass("Enter your OTF password: ") def main(): - with Otf(USERNAME, PASSWORD) as otf: - resp = otf.get_member_lifetime_stats() - print(resp.model_dump_json(indent=4)) + user = OtfUser.login(USERNAME, PASSWORD) + otf = Otf(user=user) - resp = otf.get_body_composition_list() - print(resp.data[0].model_dump_json(indent=4)) + resp = otf.get_member_lifetime_stats() + print(resp.model_dump_json(indent=4)) - # performance summaries are historical records of your performance in workouts - # `get_performance_summaries` takes a limit (default of 30) and returns a list of summaries - data_list = otf.get_performance_summaries() - print(data_list.summaries[0].model_dump_json(indent=4)) - """ - { - "performance_summary_id": "29dd97f4-3418-4247-b35c-37eabc5e17f3", - "details": { - "calories_burned": 506, - "splat_points": 18, - "step_count": 0, - "active_time_seconds": 3413, - "zone_time_minutes": { - "gray": 2, - "blue": 13, - "green": 24, - "orange": 16, - "red": 2 - } - }, - "ratable": true, - "otf_class": { - "ot_base_class_uuid": "b6549fc2-a479-4b03-9303-e0e45dbcd8c9", - "starts_at_local": "2024-06-11T09:45:00", - "name": "Orange 60 Min 2G", - "coach": ..., - "studio": ..., - }, - "ratings": null - } - """ + resp = otf.get_body_composition_list() + print(resp.data[0].model_dump_json(indent=4)) - # you can get detailed information about a specific performance summary by calling `get_performance_summary` - # which takes a performance_summary_id as an argument - data = otf.get_performance_summary(data_list.summaries[0].id) - print(data.model_dump_json(indent=4)) + # performance summaries are historical records of your performance in workouts + # `get_performance_summaries` takes a limit (default of 30) and returns a list of summaries + data_list = otf.get_performance_summaries() + print(data_list.summaries[0].model_dump_json(indent=4)) + """ + { + "performance_summary_id": "29dd97f4-3418-4247-b35c-37eabc5e17f3", + "details": { + "calories_burned": 506, + "splat_points": 18, + "step_count": 0, + "active_time_seconds": 3413, + "zone_time_minutes": { + "gray": 2, + "blue": 13, + "green": 24, + "orange": 16, + "red": 2 + } + }, + "ratable": true, + "otf_class": { + "ot_base_class_uuid": "b6549fc2-a479-4b03-9303-e0e45dbcd8c9", + "starts_at_local": "2024-06-11T09:45:00", + "name": "Orange 60 Min 2G", + "coach": ..., + "studio": ..., + }, + "ratings": null + } + """ - """ - { - "class_history_uuid": "29dd97f4-3418-4247-b35c-37eabc5e17f3", - "details": { - "calories_burned": 506, - "splat_points": 18, - "step_count": 3314, - "active_time_seconds": 0, - "zone_time_minutes": { - "gray": 2, - "blue": 13, - "green": 24, - "orange": 16, - "red": 2 - }, - "heart_rate": { - "max_hr": 0, - "peak_hr": 180, - "peak_hr_percent": 94, - "avg_hr": 149, - "avg_hr_percent": 78 - }, - "equipment_data": { - "treadmill": { - "avg_pace": { - "display_value": "15:23", - "display_unit": "min/mile", - "metric_value": "923" - }, - "avg_speed": { - "display_value": 3.9, - "display_unit": "mph", - "metric_value": 3.9 - }, - "max_pace": ..., - "max_speed": ..., - "moving_time": ..., - "total_distance": ..., - "avg_incline": ..., - "elevation_gained": ..., - "max_incline": ... - }, - "rower": ... - } + # you can get detailed information about a specific performance summary by calling `get_performance_summary` + # which takes a performance_summary_id as an argument + data = otf.get_performance_summary(data_list.summaries[0].id) + print(data.model_dump_json(indent=4)) + + """ + { + "class_history_uuid": "29dd97f4-3418-4247-b35c-37eabc5e17f3", + "details": { + "calories_burned": 506, + "splat_points": 18, + "step_count": 3314, + "active_time_seconds": 0, + "zone_time_minutes": { + "gray": 2, + "blue": 13, + "green": 24, + "orange": 16, + "red": 2 + }, + "heart_rate": { + "max_hr": 0, + "peak_hr": 180, + "peak_hr_percent": 94, + "avg_hr": 149, + "avg_hr_percent": 78 }, - "ratable": false, - "otf_class": { - "starts_at_local": "2024-06-11T09:45:00", - "name": "Orange 60 Min 2G" + "equipment_data": { + "treadmill": { + "avg_pace": { + "display_value": "15:23", + "display_unit": "min/mile", + "metric_value": "923" + }, + "avg_speed": { + "display_value": 3.9, + "display_unit": "mph", + "metric_value": 3.9 + }, + "max_pace": ..., + "max_speed": ..., + "moving_time": ..., + "total_distance": ..., + "avg_incline": ..., + "elevation_gained": ..., + "max_incline": ... + }, + "rower": ... } + }, + "ratable": false, + "otf_class": { + "starts_at_local": "2024-06-11T09:45:00", + "name": "Orange 60 Min 2G" } - """ + } + """ - # telemetry is a detailed record of a specific workout - minute by minute, or more granular if desired - # this endpoint takes a class_history_uuid, as well as a number of max data points - if you do not pass - # this value it will attempt to return enough data points for 30 second intervals + # telemetry is a detailed record of a specific workout - minute by minute, or more granular if desired + # this endpoint takes a class_history_uuid, as well as a number of max data points - if you do not pass + # this value it will attempt to return enough data points for 30 second intervals - telemetry = otf.get_telemetry(performance_summary_id=data_list.summaries[0].id) - telemetry.telemetry = telemetry.telemetry[:2] - print(telemetry.model_dump_json(indent=4)) + telemetry = otf.get_telemetry(performance_summary_id=data_list.summaries[0].id) + telemetry.telemetry = telemetry.telemetry[:2] + print(telemetry.model_dump_json(indent=4)) - """ - { - "member_uuid": "fa323d40-bfae-4e72-872c-e11188d182a7", - "class_history_uuid": "5945a723-930b-449a-bd8f-8267a4ff392f", - "class_start_time": "2024-06-11 14:46:07+00:00", - "max_hr": 191, - "zones": { - "gray": { - "start_bpm": 96, - "end_bpm": 116 - }, - "blue": { - "start_bpm": 117, - "end_bpm": 135 - }, - "green": { - "start_bpm": 136, - "end_bpm": 159 - }, - "orange": { - "start_bpm": 160, - "end_bpm": 175 + """ + { + "member_uuid": "fa323d40-bfae-4e72-872c-e11188d182a7", + "class_history_uuid": "5945a723-930b-449a-bd8f-8267a4ff392f", + "class_start_time": "2024-06-11 14:46:07+00:00", + "max_hr": 191, + "zones": { + "gray": { + "start_bpm": 96, + "end_bpm": 116 + }, + "blue": { + "start_bpm": 117, + "end_bpm": 135 + }, + "green": { + "start_bpm": 136, + "end_bpm": 159 + }, + "orange": { + "start_bpm": 160, + "end_bpm": 175 + }, + "red": { + "start_bpm": 176, + "end_bpm": 191 + } + }, + "window_size": 30, + "telemetry": [ + { + "relative_timestamp": 0, + "hr": 105, + "agg_splats": 0, + "agg_calories": 2, + "timestamp": "2024-06-11 14:46:07+00:00", + "tread_data": { + "tread_speed": 1.34, + "tread_incline": 1.0, + "agg_tread_distance": 9 }, - "red": { - "start_bpm": 176, - "end_bpm": 191 + "row_data": { + "row_speed": 1.0, + "row_pps": 0.0, + "row_Spm": 0.0, + "agg_row_distance": 0, + "row_pace": 0 } }, - "window_size": 30, - "telemetry": [ - { - "relative_timestamp": 0, - "hr": 105, - "agg_splats": 0, - "agg_calories": 2, - "timestamp": "2024-06-11 14:46:07+00:00", - "tread_data": { - "tread_speed": 1.34, - "tread_incline": 1.0, - "agg_tread_distance": 9 - }, - "row_data": { - "row_speed": 1.0, - "row_pps": 0.0, - "row_Spm": 0.0, - "agg_row_distance": 0, - "row_pace": 0 - } - }, - { - "relative_timestamp": 30, - "hr": 132, - "agg_splats": 0, - "agg_calories": 4, - "timestamp": "2024-06-11 14:46:37+00:00", - "tread_data": { - "tread_speed": 2.46, - "tread_incline": 1.0, - "agg_tread_distance": 62 - }, - "row_data": { - "row_speed": 1.0, - "row_pps": 0.0, - "row_Spm": 0.0, - "agg_row_distance": 0, - "row_pace": 0 - } + { + "relative_timestamp": 30, + "hr": 132, + "agg_splats": 0, + "agg_calories": 4, + "timestamp": "2024-06-11 14:46:37+00:00", + "tread_data": { + "tread_speed": 2.46, + "tread_incline": 1.0, + "agg_tread_distance": 62 }, - ... - ] - } - """ + "row_data": { + "row_speed": 1.0, + "row_pps": 0.0, + "row_Spm": 0.0, + "agg_row_distance": 0, + "row_pace": 0 + } + }, + ... + ] + } + """ if __name__ == "__main__":