otf-api 0.12.0__tar.gz → 0.13.0__tar.gz

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 (85) hide show
  1. {otf_api-0.12.0/src/otf_api.egg-info → otf_api-0.13.0}/PKG-INFO +4 -2
  2. {otf_api-0.12.0 → otf_api-0.13.0}/pyproject.toml +17 -19
  3. otf_api-0.13.0/src/otf_api/__init__.py +42 -0
  4. otf_api-0.13.0/src/otf_api/api/__init__.py +3 -0
  5. otf_api-0.13.0/src/otf_api/api/_compat.py +77 -0
  6. otf_api-0.13.0/src/otf_api/api/api.py +80 -0
  7. otf_api-0.13.0/src/otf_api/api/bookings/__init__.py +3 -0
  8. otf_api-0.13.0/src/otf_api/api/bookings/booking_api.py +541 -0
  9. otf_api-0.13.0/src/otf_api/api/bookings/booking_client.py +112 -0
  10. otf_api-0.13.0/src/otf_api/api/client.py +203 -0
  11. otf_api-0.13.0/src/otf_api/api/members/__init__.py +3 -0
  12. otf_api-0.13.0/src/otf_api/api/members/member_api.py +187 -0
  13. otf_api-0.13.0/src/otf_api/api/members/member_client.py +112 -0
  14. otf_api-0.13.0/src/otf_api/api/studios/__init__.py +3 -0
  15. otf_api-0.13.0/src/otf_api/api/studios/studio_api.py +173 -0
  16. otf_api-0.13.0/src/otf_api/api/studios/studio_client.py +120 -0
  17. otf_api-0.13.0/src/otf_api/api/utils.py +307 -0
  18. otf_api-0.13.0/src/otf_api/api/workouts/__init__.py +3 -0
  19. otf_api-0.13.0/src/otf_api/api/workouts/workout_api.py +333 -0
  20. otf_api-0.13.0/src/otf_api/api/workouts/workout_client.py +140 -0
  21. otf_api-0.13.0/src/otf_api/auth/__init__.py +4 -0
  22. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api/auth/auth.py +155 -89
  23. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api/auth/user.py +5 -17
  24. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api/auth/utils.py +27 -2
  25. otf_api-0.13.0/src/otf_api/cache.py +132 -0
  26. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api/exceptions.py +18 -6
  27. otf_api-0.13.0/src/otf_api/models/__init__.py +68 -0
  28. otf_api-0.13.0/src/otf_api/models/bookings/__init__.py +23 -0
  29. otf_api-0.13.0/src/otf_api/models/bookings/bookings.py +134 -0
  30. {otf_api-0.12.0/src/otf_api/models → otf_api-0.13.0/src/otf_api/models/bookings}/bookings_v2.py +72 -31
  31. otf_api-0.13.0/src/otf_api/models/bookings/classes.py +124 -0
  32. {otf_api-0.12.0/src/otf_api/models → otf_api-0.13.0/src/otf_api/models/bookings}/enums.py +7 -81
  33. {otf_api-0.12.0/src/otf_api → otf_api-0.13.0/src/otf_api/models/bookings}/filters.py +39 -11
  34. {otf_api-0.12.0/src/otf_api/models → otf_api-0.13.0/src/otf_api/models/bookings}/ratings.py +2 -6
  35. otf_api-0.13.0/src/otf_api/models/members/__init__.py +5 -0
  36. otf_api-0.13.0/src/otf_api/models/members/member_detail.py +149 -0
  37. otf_api-0.13.0/src/otf_api/models/members/member_membership.py +26 -0
  38. otf_api-0.13.0/src/otf_api/models/members/member_purchases.py +29 -0
  39. otf_api-0.13.0/src/otf_api/models/members/notifications.py +17 -0
  40. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api/models/mixins.py +48 -1
  41. otf_api-0.13.0/src/otf_api/models/studios/__init__.py +5 -0
  42. otf_api-0.13.0/src/otf_api/models/studios/enums.py +11 -0
  43. otf_api-0.13.0/src/otf_api/models/studios/studio_detail.py +93 -0
  44. otf_api-0.13.0/src/otf_api/models/studios/studio_services.py +36 -0
  45. otf_api-0.13.0/src/otf_api/models/workouts/__init__.py +31 -0
  46. {otf_api-0.12.0/src/otf_api/models → otf_api-0.13.0/src/otf_api/models/workouts}/body_composition_list.py +140 -71
  47. otf_api-0.13.0/src/otf_api/models/workouts/challenge_tracker_content.py +50 -0
  48. otf_api-0.13.0/src/otf_api/models/workouts/challenge_tracker_detail.py +99 -0
  49. otf_api-0.13.0/src/otf_api/models/workouts/enums.py +70 -0
  50. otf_api-0.13.0/src/otf_api/models/workouts/lifetime_stats.py +96 -0
  51. otf_api-0.13.0/src/otf_api/models/workouts/out_of_studio_workout_history.py +32 -0
  52. {otf_api-0.12.0/src/otf_api/models → otf_api-0.13.0/src/otf_api/models/workouts}/performance_summary.py +19 -5
  53. otf_api-0.13.0/src/otf_api/models/workouts/telemetry.py +88 -0
  54. {otf_api-0.12.0/src/otf_api/models → otf_api-0.13.0/src/otf_api/models/workouts}/workout.py +34 -20
  55. {otf_api-0.12.0 → otf_api-0.13.0/src/otf_api.egg-info}/PKG-INFO +4 -2
  56. otf_api-0.13.0/src/otf_api.egg-info/SOURCES.txt +63 -0
  57. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api.egg-info/requires.txt +3 -0
  58. {otf_api-0.12.0 → otf_api-0.13.0}/tests/test_filters.py +1 -2
  59. otf_api-0.12.0/src/otf_api/__init__.py +0 -10
  60. otf_api-0.12.0/src/otf_api/api.py +0 -1682
  61. otf_api-0.12.0/src/otf_api/auth/__init__.py +0 -4
  62. otf_api-0.12.0/src/otf_api/logging.py +0 -19
  63. otf_api-0.12.0/src/otf_api/models/__init__.py +0 -64
  64. otf_api-0.12.0/src/otf_api/models/bookings.py +0 -109
  65. otf_api-0.12.0/src/otf_api/models/challenge_tracker_content.py +0 -59
  66. otf_api-0.12.0/src/otf_api/models/challenge_tracker_detail.py +0 -88
  67. otf_api-0.12.0/src/otf_api/models/classes.py +0 -70
  68. otf_api-0.12.0/src/otf_api/models/lifetime_stats.py +0 -78
  69. otf_api-0.12.0/src/otf_api/models/member_detail.py +0 -121
  70. otf_api-0.12.0/src/otf_api/models/member_membership.py +0 -26
  71. otf_api-0.12.0/src/otf_api/models/member_purchases.py +0 -29
  72. otf_api-0.12.0/src/otf_api/models/notifications.py +0 -17
  73. otf_api-0.12.0/src/otf_api/models/out_of_studio_workout_history.py +0 -32
  74. otf_api-0.12.0/src/otf_api/models/studio_detail.py +0 -71
  75. otf_api-0.12.0/src/otf_api/models/studio_services.py +0 -36
  76. otf_api-0.12.0/src/otf_api/models/telemetry.py +0 -84
  77. otf_api-0.12.0/src/otf_api/utils.py +0 -164
  78. otf_api-0.12.0/src/otf_api.egg-info/SOURCES.txt +0 -42
  79. {otf_api-0.12.0 → otf_api-0.13.0}/LICENSE +0 -0
  80. {otf_api-0.12.0 → otf_api-0.13.0}/README.md +0 -0
  81. {otf_api-0.12.0 → otf_api-0.13.0}/setup.cfg +0 -0
  82. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api/models/base.py +0 -0
  83. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api/py.typed +0 -0
  84. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api.egg-info/dependency_links.txt +0 -0
  85. {otf_api-0.12.0 → otf_api-0.13.0}/src/otf_api.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: otf-api
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: Python OrangeTheory Fitness API Client
5
5
  Author-email: Jessica Smith <j.smith.git1@gmail.com>
6
6
  License-Expression: MIT
@@ -9,7 +9,6 @@ Classifier: Development Status :: 4 - Beta
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
- Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
13
12
  Classifier: Topic :: Software Development :: Libraries
14
13
  Classifier: Topic :: Internet :: WWW/HTTP
15
14
  Classifier: Operating System :: OS Independent
@@ -27,6 +26,9 @@ Requires-Dist: yarl<2,>=1.18.3
27
26
  Requires-Dist: tenacity<10,>=9.0.0
28
27
  Requires-Dist: cachetools>=5.5.0
29
28
  Requires-Dist: pendulum>=3.1.0
29
+ Requires-Dist: diskcache>=5.6.3
30
+ Requires-Dist: platformdirs>=4.3.6
31
+ Requires-Dist: packaging>=24.2
30
32
  Dynamic: license-file
31
33
 
32
34
  Simple API client for interacting with the OrangeTheory Fitness APIs.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "otf-api"
3
- version = "0.12.0"
3
+ version = "0.13.0"
4
4
  description = "Python OrangeTheory Fitness API Client"
5
5
  authors = [{ name = "Jessica Smith", email = "j.smith.git1@gmail.com" }]
6
6
  requires-python = ">=3.11"
@@ -11,7 +11,6 @@ classifiers = [
11
11
  "Intended Audience :: Developers",
12
12
  "Programming Language :: Python :: 3.11",
13
13
  "Topic :: Software Development :: Libraries :: Python Modules",
14
- "Topic :: Software Development :: Libraries :: Application Frameworks",
15
14
  "Topic :: Software Development :: Libraries",
16
15
  "Topic :: Internet :: WWW/HTTP",
17
16
  "Operating System :: OS Independent",
@@ -28,6 +27,9 @@ dependencies = [
28
27
  "tenacity>=9.0.0,<10",
29
28
  "cachetools>=5.5.0",
30
29
  "pendulum>=3.1.0",
30
+ "diskcache>=5.6.3",
31
+ "platformdirs>=4.3.6",
32
+ "packaging>=24.2",
31
33
  ]
32
34
 
33
35
  [project.urls]
@@ -47,23 +49,19 @@ dev = [
47
49
  "twine==5.1.1",
48
50
  ]
49
51
  docs = [
50
- "griffe<1.0.0",
51
- "griffe-fieldz==0.1.2",
52
- "mike==2.1.1",
53
- "mkdocs==1.6.0",
54
- "mkdocs-autorefs==1.0.1",
55
- "mkdocs-gen-files==0.5.0",
56
- "mkdocs-get-deps==0.2.0",
57
- "mkdocs-include-markdown-plugin==6.2.0",
58
- "mkdocs-literate-nav==0.6.1",
59
- "mkdocs-material==9.5.26",
60
- "mkdocs-material-extensions==1.3.1",
61
- "mkdocs-section-index==0.3.9",
62
- "mkdocstrings==0.25.1",
63
- "mkdocstrings-python==1.10.3",
64
- "pkginfo<1.11",
65
- "setuptools>=70.0.0,<71",
66
- "virtualenv>=20.26.2,<21",
52
+ "autodoc-pydantic>=2.2.0",
53
+ "furo>=2024.8.6",
54
+ "sphinx==8.3.0",
55
+ "sphinx-autobuild==2024.10.3",
56
+ "sphinx-autodoc-typehints==3.2.0",
57
+ "sphinx-autodoc2==0.5.0",
58
+ "sphinx-basic-ng==1.0.0b2",
59
+ "sphinxcontrib-applehelp==2.0.0",
60
+ "sphinxcontrib-devhelp==2.0.0",
61
+ "sphinxcontrib-htmlhelp==2.1.0",
62
+ "sphinxcontrib-jsmath==1.0.1",
63
+ "sphinxcontrib-qthelp==2.0.0",
64
+ "sphinxcontrib-serializinghtml==2.0.0",
67
65
  ]
68
66
 
69
67
  [tool.uv]
@@ -0,0 +1,42 @@
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
9
+
10
+ from otf_api import models
11
+ from otf_api.api import Otf
12
+ from otf_api.auth import OtfUser
13
+
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"
40
+
41
+
42
+ __all__ = ["Otf", "OtfUser", "models"]
@@ -0,0 +1,3 @@
1
+ from otf_api.api.api import Otf
2
+
3
+ __all__ = ["Otf"]
@@ -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))
@@ -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"]