otf-api 0.12.1__py3-none-any.whl → 0.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. otf_api/__init__.py +35 -3
  2. otf_api/api/__init__.py +3 -0
  3. otf_api/api/_compat.py +77 -0
  4. otf_api/api/api.py +80 -0
  5. otf_api/api/bookings/__init__.py +3 -0
  6. otf_api/api/bookings/booking_api.py +541 -0
  7. otf_api/api/bookings/booking_client.py +112 -0
  8. otf_api/api/client.py +203 -0
  9. otf_api/api/members/__init__.py +3 -0
  10. otf_api/api/members/member_api.py +187 -0
  11. otf_api/api/members/member_client.py +112 -0
  12. otf_api/api/studios/__init__.py +3 -0
  13. otf_api/api/studios/studio_api.py +173 -0
  14. otf_api/api/studios/studio_client.py +120 -0
  15. otf_api/api/utils.py +307 -0
  16. otf_api/api/workouts/__init__.py +3 -0
  17. otf_api/api/workouts/workout_api.py +333 -0
  18. otf_api/api/workouts/workout_client.py +140 -0
  19. otf_api/auth/__init__.py +1 -1
  20. otf_api/auth/auth.py +155 -89
  21. otf_api/auth/user.py +5 -17
  22. otf_api/auth/utils.py +27 -2
  23. otf_api/cache.py +132 -0
  24. otf_api/exceptions.py +18 -6
  25. otf_api/models/__init__.py +25 -21
  26. otf_api/models/bookings/__init__.py +23 -0
  27. otf_api/models/bookings/bookings.py +134 -0
  28. otf_api/models/{bookings_v2.py → bookings/bookings_v2.py} +72 -31
  29. otf_api/models/bookings/classes.py +124 -0
  30. otf_api/models/{enums.py → bookings/enums.py} +7 -81
  31. otf_api/{filters.py → models/bookings/filters.py} +39 -11
  32. otf_api/models/{ratings.py → bookings/ratings.py} +2 -6
  33. otf_api/models/members/__init__.py +5 -0
  34. otf_api/models/members/member_detail.py +149 -0
  35. otf_api/models/members/member_membership.py +26 -0
  36. otf_api/models/members/member_purchases.py +29 -0
  37. otf_api/models/members/notifications.py +17 -0
  38. otf_api/models/mixins.py +48 -1
  39. otf_api/models/studios/__init__.py +5 -0
  40. otf_api/models/studios/enums.py +11 -0
  41. otf_api/models/studios/studio_detail.py +93 -0
  42. otf_api/models/studios/studio_services.py +36 -0
  43. otf_api/models/workouts/__init__.py +31 -0
  44. otf_api/models/{body_composition_list.py → workouts/body_composition_list.py} +140 -71
  45. otf_api/models/workouts/challenge_tracker_content.py +50 -0
  46. otf_api/models/workouts/challenge_tracker_detail.py +99 -0
  47. otf_api/models/workouts/enums.py +70 -0
  48. otf_api/models/workouts/lifetime_stats.py +96 -0
  49. otf_api/models/workouts/out_of_studio_workout_history.py +32 -0
  50. otf_api/models/{performance_summary.py → workouts/performance_summary.py} +19 -5
  51. otf_api/models/workouts/telemetry.py +88 -0
  52. otf_api/models/{workout.py → workouts/workout.py} +34 -20
  53. {otf_api-0.12.1.dist-info → otf_api-0.13.0.dist-info}/METADATA +4 -2
  54. otf_api-0.13.0.dist-info/RECORD +59 -0
  55. {otf_api-0.12.1.dist-info → otf_api-0.13.0.dist-info}/WHEEL +1 -1
  56. otf_api/api.py +0 -1682
  57. otf_api/logging.py +0 -19
  58. otf_api/models/bookings.py +0 -109
  59. otf_api/models/challenge_tracker_content.py +0 -59
  60. otf_api/models/challenge_tracker_detail.py +0 -88
  61. otf_api/models/classes.py +0 -70
  62. otf_api/models/lifetime_stats.py +0 -78
  63. otf_api/models/member_detail.py +0 -121
  64. otf_api/models/member_membership.py +0 -26
  65. otf_api/models/member_purchases.py +0 -29
  66. otf_api/models/notifications.py +0 -17
  67. otf_api/models/out_of_studio_workout_history.py +0 -32
  68. otf_api/models/studio_detail.py +0 -71
  69. otf_api/models/studio_services.py +0 -36
  70. otf_api/models/telemetry.py +0 -84
  71. otf_api/utils.py +0 -164
  72. otf_api-0.12.1.dist-info/RECORD +0 -38
  73. {otf_api-0.12.1.dist-info → otf_api-0.13.0.dist-info}/licenses/LICENSE +0 -0
  74. {otf_api-0.12.1.dist-info → otf_api-0.13.0.dist-info}/top_level.txt +0 -0
otf_api/__init__.py CHANGED
@@ -1,10 +1,42 @@
1
- from otf_api import logging # noqa # type: ignore
1
+ """Unofficial Orangetheory API client.
2
+
3
+ This software is not affiliated with, endorsed by, or supported by Orangetheory Fitness.
4
+ Use it at your own risk. It may break at any time if Orangetheory changes their services.
5
+ """
6
+
7
+ import logging
8
+ import os
2
9
 
3
- from otf_api.api import Otf
4
10
  from otf_api import models
11
+ from otf_api.api import Otf
5
12
  from otf_api.auth import OtfUser
6
13
 
7
- __version__ = "0.12.1"
14
+ LOG_LEVEL = os.getenv("OTF_LOG_LEVEL", "INFO").upper()
15
+ LOG_LEVEL_NUM = getattr(logging, LOG_LEVEL, logging.INFO)
16
+ LOG_FMT = "{asctime} - {module}.{funcName}:{lineno} - {levelname} - {message}"
17
+ DATE_FMT = "%Y-%m-%d %H:%M:%S%z"
18
+
19
+
20
+ def _setup_logging() -> None:
21
+ logger = logging.getLogger("otf_api")
22
+
23
+ if logger.handlers:
24
+ return # Already set up
25
+
26
+ # 2) Set the logger level to INFO (or whatever you need).
27
+ logger.setLevel(LOG_LEVEL_NUM)
28
+
29
+ # 3) Create a handler (e.g., console) and set its formatter.
30
+ handler = logging.StreamHandler()
31
+ handler.setFormatter(logging.Formatter(fmt=LOG_FMT, datefmt=DATE_FMT, style="{"))
32
+
33
+ # 4) Add this handler to your package logger.
34
+ logger.addHandler(handler)
35
+
36
+
37
+ _setup_logging()
38
+
39
+ __version__ = "0.13.0"
8
40
 
9
41
 
10
42
  __all__ = ["Otf", "OtfUser", "models"]
@@ -0,0 +1,3 @@
1
+ from otf_api.api.api import Otf
2
+
3
+ __all__ = ["Otf"]
otf_api/api/_compat.py ADDED
@@ -0,0 +1,77 @@
1
+ # ruff: noqa
2
+
3
+
4
+ import warnings
5
+
6
+
7
+ _LEGACY_METHOD_MAP = {
8
+ "book_class": "bookings.book_class",
9
+ "book_class_new": "bookings.book_class_new",
10
+ "cancel_booking": "bookings.cancel_booking",
11
+ "cancel_booking_new": "bookings.cancel_booking_new",
12
+ "get_booking": "bookings.get_booking",
13
+ "get_booking_from_class": "bookings.get_booking_from_class",
14
+ "get_booking_from_class_new": "bookings.get_booking_from_class_new",
15
+ "get_booking_new": "bookings.get_booking_new",
16
+ "get_bookings": "bookings.get_bookings",
17
+ "get_bookings_new": "bookings.get_bookings_new",
18
+ "get_bookings_new_by_date": "bookings.get_bookings_new_by_date",
19
+ "get_classes": "bookings.get_classes",
20
+ "get_historical_bookings": "bookings.get_historical_bookings",
21
+ "rate_class": "bookings.rate_class",
22
+ "get_email_notification_settings": "members.get_email_notification_settings",
23
+ "get_member_detail": "members.get_member_detail",
24
+ "get_member_membership": "members.get_member_membership",
25
+ "get_member_purchases": "members.get_member_purchases",
26
+ "get_sms_notification_settings": "members.get_sms_notification_settings",
27
+ "update_email_notification_settings": "members.update_email_notification_settings",
28
+ "update_member_name": "members.update_member_name",
29
+ "update_sms_notification_settings": "members.update_sms_notification_settings",
30
+ "add_favorite_studio": "studios.add_favorite_studio",
31
+ "get_favorite_studios": "studios.get_favorite_studios",
32
+ "get_studio_detail": "studios.get_studio_detail",
33
+ "get_studio_services": "studios.get_studio_services",
34
+ "get_studios_by_geo": "studios.get_studios_by_geo",
35
+ "remove_favorite_studio": "studios.remove_favorite_studio",
36
+ "search_studios_by_geo": "studios.search_studios_by_geo",
37
+ "get_benchmarks": "workouts.get_benchmarks",
38
+ "get_benchmarks_by_challenge_category": "workouts.get_benchmarks_by_challenge_category",
39
+ "get_benchmarks_by_equipment": "workouts.get_benchmarks_by_equipment",
40
+ "get_body_composition_list": "workouts.get_body_composition_list",
41
+ "get_challenge_tracker": "workouts.get_challenge_tracker",
42
+ "get_challenge_tracker_detail": "workouts.get_challenge_tracker_detail",
43
+ "get_hr_history": "workouts.get_hr_history",
44
+ "get_member_lifetime_stats_in_studio": "workouts.get_member_lifetime_stats_in_studio",
45
+ "get_member_lifetime_stats_out_of_studio": "workouts.get_member_lifetime_stats_out_of_studio",
46
+ "get_out_of_studio_workout_history": "workouts.get_out_of_studio_workout_history",
47
+ "get_performance_summary": "workouts.get_performance_summary",
48
+ "get_telemetry": "workouts.get_telemetry",
49
+ "get_workout_from_booking": "workouts.get_workout_from_booking",
50
+ "get_workouts": "workouts.get_workouts",
51
+ "rate_class_from_workout": "workouts.rate_class_from_workout",
52
+ }
53
+
54
+
55
+ def _generate_legacy_method(old_name, new_path):
56
+ def method(self, *args, **kwargs):
57
+ warnings.warn(
58
+ f"`Otf.{old_name}()` is deprecated. Use `Otf.{new_path}()` instead.", DeprecationWarning, stacklevel=2
59
+ )
60
+
61
+ # Resolve nested attributes like 'bookings.cancel'
62
+ target = self
63
+ for attr in new_path.split("."):
64
+ target = getattr(target, attr)
65
+
66
+ return target(*args, **kwargs)
67
+
68
+ method.__name__ = old_name
69
+ return method
70
+
71
+
72
+ class _LegacyCompatMixin:
73
+ pass
74
+
75
+
76
+ for legacy_name, new_path in _LEGACY_METHOD_MAP.items():
77
+ setattr(_LegacyCompatMixin, legacy_name, _generate_legacy_method(legacy_name, new_path))
otf_api/api/api.py ADDED
@@ -0,0 +1,80 @@
1
+ from otf_api import models
2
+ from otf_api.api._compat import _LegacyCompatMixin
3
+ from otf_api.auth import OtfUser
4
+
5
+ from .bookings import BookingApi
6
+ from .client import OtfClient
7
+ from .members import MemberApi
8
+ from .studios import StudioApi
9
+ from .workouts import WorkoutApi
10
+
11
+ # TODO: clean up docs and turn on autodoc when we get rig of _LegacyCompatMixin
12
+
13
+
14
+ class Otf(_LegacyCompatMixin):
15
+ """The main OTF API client.
16
+
17
+ This class handles serialization and enrichment of data from the OTF API. The actual requests to the OTF API are\
18
+ handled by separate client classes. This class provides methods to get bookings, classes, member details, and more.
19
+ It also provides methods to book and cancel classes, get member stats, and manage favorite studios.
20
+
21
+ It is designed to be used with an authenticated user, which can be provided as an `OtfUser` object. If no user is\
22
+ provided, the `OtfClient` class will attempt to use cached credentials, environment variables, or prompt the user\
23
+ for credentials.
24
+ """
25
+
26
+ bookings: BookingApi
27
+ members: MemberApi
28
+ workouts: WorkoutApi
29
+ studios: StudioApi
30
+
31
+ def __init__(self, user: OtfUser | None = None):
32
+ """Initialize the OTF API client.
33
+
34
+ Args:
35
+ user (OtfUser): The user to authenticate as.
36
+ """
37
+ client = OtfClient(user)
38
+
39
+ self.bookings = BookingApi(self, client)
40
+ self.members = MemberApi(self, client)
41
+ self.workouts = WorkoutApi(self, client)
42
+ self.studios = StudioApi(self, client)
43
+
44
+ self._member: models.MemberDetail | None = None
45
+
46
+ @property
47
+ def member_uuid(self) -> str:
48
+ """Get the member UUID."""
49
+ return self.member.member_uuid
50
+
51
+ @property
52
+ def member(self) -> models.MemberDetail:
53
+ """Get the member details.
54
+
55
+ This will lazily load the member details if they have not been loaded yet.
56
+ """
57
+ if self._member is None:
58
+ self._member = self.members.get_member_detail()
59
+ return self._member
60
+
61
+ @property
62
+ def home_studio(self) -> models.StudioDetail:
63
+ """Get the home studio details."""
64
+ return self.member.home_studio
65
+
66
+ @property
67
+ def home_studio_uuid(self) -> str:
68
+ """Get the home studio UUID."""
69
+ return self.home_studio.studio_uuid
70
+
71
+ def refresh_member(self) -> models.MemberDetail:
72
+ """Refresh the member details.
73
+
74
+ This will reload the member details from the OTF API.
75
+
76
+ Returns:
77
+ MemberDetail: The refreshed member details.
78
+ """
79
+ self._member = self.members.get_member_detail()
80
+ return self._member
@@ -0,0 +1,3 @@
1
+ from .booking_api import BookingApi
2
+
3
+ __all__ = ["BookingApi"]