otf-api 0.12.0__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.0.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.0.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.0.dist-info/RECORD +0 -38
  73. {otf_api-0.12.0.dist-info → otf_api-0.13.0.dist-info}/licenses/LICENSE +0 -0
  74. {otf_api-0.12.0.dist-info → otf_api-0.13.0.dist-info}/top_level.txt +0 -0
@@ -22,15 +22,27 @@ class HeartRate(OtfItemBase):
22
22
 
23
23
 
24
24
  class PerformanceMetric(OtfItemBase):
25
- display_value: time | float
25
+ display_value: time | float | None
26
26
  display_unit: str
27
27
  metric_value: float
28
28
 
29
+ def __str__(self) -> str:
30
+ """Return a string representation of the PerformanceMetric."""
31
+ return f"{self.display_value} {self.display_unit}"
32
+
29
33
  @field_validator("display_value", mode="before")
30
34
  @classmethod
31
- def convert_to_time_format(cls, value) -> time | float:
35
+ def convert_to_time_format(cls, value: str | None | float | int) -> time | float | None:
36
+ """Convert display_value to a time object if it is in the format of HH:MM:SS or MM:SS.
37
+
38
+ Args:
39
+ value (str | None | float | int): The value to convert.
40
+
41
+ Returns:
42
+ time | float: The converted value, or the original value if it is not in the expected format.
43
+ """
32
44
  if not value:
33
- return value
45
+ return None
34
46
 
35
47
  if isinstance(value, float | int):
36
48
  return value
@@ -73,8 +85,10 @@ class PerformanceSummary(OtfItemBase):
73
85
  You likely want to use the `Workout` model and `get_workouts` method instead.
74
86
  """
75
87
 
76
- performance_summary_id: str = Field(..., alias="id", description="Unique identifier for this performance summary")
77
- class_history_uuid: str = Field(..., alias="id", description="Same as performance_summary_id")
88
+ performance_summary_id: str = Field(
89
+ ..., validation_alias="id", description="Unique identifier for this performance summary"
90
+ )
91
+ class_history_uuid: str = Field(..., validation_alias="id", description="Same as performance_summary_id")
78
92
  ratable: bool | None = None
79
93
 
80
94
  calories_burned: int | None = Field(None, validation_alias=AliasPath("details", "calories_burned"))
@@ -0,0 +1,88 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import Any
3
+
4
+ from pydantic import AliasPath, Field, field_serializer
5
+
6
+ from otf_api.models.base import OtfItemBase
7
+
8
+
9
+ class Zone(OtfItemBase):
10
+ start_bpm: int = Field(..., validation_alias="startBpm")
11
+ end_bpm: int = Field(..., validation_alias="endBpm")
12
+
13
+
14
+ class Zones(OtfItemBase):
15
+ gray: Zone
16
+ blue: Zone
17
+ green: Zone
18
+ orange: Zone
19
+ red: Zone
20
+
21
+
22
+ class TreadData(OtfItemBase):
23
+ tread_speed: float = Field(..., validation_alias="treadSpeed")
24
+ tread_incline: float = Field(..., validation_alias="treadIncline")
25
+ agg_tread_distance: int = Field(..., validation_alias="aggTreadDistance")
26
+
27
+
28
+ class RowData(OtfItemBase):
29
+ row_speed: float = Field(..., validation_alias="rowSpeed")
30
+ row_pps: float = Field(..., validation_alias="rowPps")
31
+ row_spm: float = Field(..., validation_alias="rowSpm")
32
+ agg_row_distance: int = Field(..., validation_alias="aggRowDistance")
33
+ row_pace: int = Field(..., validation_alias="rowPace")
34
+
35
+
36
+ class TelemetryItem(OtfItemBase):
37
+ relative_timestamp: int = Field(..., validation_alias="relativeTimestamp")
38
+ hr: int | None = None
39
+ agg_splats: int = Field(..., validation_alias="aggSplats")
40
+ agg_calories: int = Field(..., validation_alias="aggCalories")
41
+ timestamp: datetime | None = Field(
42
+ None,
43
+ init_var=False,
44
+ description="The timestamp of the telemetry item, calculated from the class start time and relative timestamp.",
45
+ )
46
+ tread_data: TreadData | None = Field(None, validation_alias="treadData")
47
+ row_data: RowData | None = Field(None, validation_alias="rowData")
48
+
49
+
50
+ class Telemetry(OtfItemBase):
51
+ member_uuid: str = Field(..., validation_alias="memberUuid")
52
+ performance_summary_id: str = Field(
53
+ ...,
54
+ validation_alias="classHistoryUuid",
55
+ description="The ID of the performance summary this telemetry item belongs to.",
56
+ )
57
+ class_history_uuid: str = Field(
58
+ ..., validation_alias="classHistoryUuid", description="The same as performance_summary_id."
59
+ )
60
+ class_start_time: datetime | None = Field(None, validation_alias="classStartTime")
61
+ max_hr: int | None = Field(None, validation_alias="maxHr")
62
+ zones: Zones
63
+ window_size: int | None = Field(None, validation_alias="windowSize")
64
+ telemetry: list[TelemetryItem] = Field(default_factory=list)
65
+
66
+ def __init__(self, **data: dict[str, Any]):
67
+ super().__init__(**data)
68
+ for telem in self.telemetry:
69
+ if self.class_start_time is None:
70
+ continue
71
+
72
+ telem.timestamp = self.class_start_time + timedelta(seconds=telem.relative_timestamp)
73
+
74
+ @field_serializer("telemetry", when_used="json")
75
+ def reduce_telemetry_list(self, value: list[TelemetryItem]) -> list[TelemetryItem]:
76
+ """Reduces the telemetry list to only include the first 10 items."""
77
+ if len(value) > 10:
78
+ return value[:5] + value[-5:]
79
+ return value
80
+
81
+
82
+ class TelemetryHistoryItem(OtfItemBase):
83
+ max_hr_type: str | None = Field(None, validation_alias=AliasPath("maxHr", "type"))
84
+ max_hr_value: int | None = Field(None, validation_alias=AliasPath("maxHr", "value"))
85
+ zones: Zones | None = None
86
+ change_from_previous: int | None = Field(None, validation_alias="changeFromPrevious")
87
+ change_bucket: str | None = Field(None, validation_alias="changeBucket")
88
+ assigned_at: datetime | None = Field(None, validation_alias="assignedAt")
@@ -1,21 +1,15 @@
1
+ from typing import Any, Literal
2
+
1
3
  from pydantic import AliasPath, Field
2
4
 
3
5
  from otf_api.models.base import OtfItemBase
4
- from otf_api.models.bookings_v2 import (
5
- BookingV2,
6
- BookingV2Class,
7
- BookingV2Studio,
8
- BookingV2Workout,
9
- Rating,
10
- ZoneTimeMinutes,
11
- )
12
- from otf_api.models.performance_summary import HeartRate, Rower, Treadmill
13
- from otf_api.models.telemetry import Telemetry
14
-
15
-
16
- class Workout(OtfItemBase):
17
- """Represents a workout - this combines the performance summary, data from the new bookings endpoint, and
18
- telemetry data.
6
+ from otf_api.models.bookings import BookingV2, BookingV2Class, BookingV2Studio, BookingV2Workout, Rating
7
+ from otf_api.models.mixins import ApiMixin
8
+ from otf_api.models.workouts import HeartRate, Rower, Telemetry, Treadmill, ZoneTimeMinutes
9
+
10
+
11
+ class Workout(ApiMixin, OtfItemBase):
12
+ """Represents a workout - combines the performance summary, data from the new bookings endpoint, and telemetry data.
19
13
 
20
14
  The final product contains all the performance summary data, the detailed data over time, as well as the class,
21
15
  coach, studio, and rating data from the new endpoint.
@@ -23,8 +17,10 @@ class Workout(OtfItemBase):
23
17
  This should match the data that is shown in the OTF app after a workout.
24
18
  """
25
19
 
26
- performance_summary_id: str = Field(..., alias="id", description="Unique identifier for this performance summary")
27
- class_history_uuid: str = Field(..., alias="id", description="Same as performance_summary_id")
20
+ performance_summary_id: str = Field(
21
+ ..., validation_alias="id", description="Unique identifier for this performance summary"
22
+ )
23
+ class_history_uuid: str = Field(..., validation_alias="id", description="Same as performance_summary_id")
28
24
  booking_id: str = Field(..., description="The booking id for the new bookings endpoint.")
29
25
  class_uuid: str | None = Field(
30
26
  None, description="Used by the ratings endpoint - seems to fall off after a few months"
@@ -72,10 +68,28 @@ class Workout(OtfItemBase):
72
68
  data["class_rating"] = v2_booking.class_rating
73
69
  data["coach_rating"] = v2_booking.coach_rating
74
70
 
75
- telemetry = data.get("telemetry")
76
- if telemetry and isinstance(telemetry, Telemetry):
71
+ telemetry: dict[str, Any] | None = data.get("telemetry")
72
+ if telemetry and "maxHr" in telemetry:
77
73
  # max_hr seems to be left out of the heart rate data - it has peak_hr but they do not match
78
74
  # so if we have telemetry data, we can get the max_hr from there
79
- data["details"]["heart_rate"]["max_hr"] = telemetry.max_hr
75
+ data["details"]["heart_rate"]["max_hr"] = telemetry["maxHr"]
80
76
 
81
77
  super().__init__(**data)
78
+
79
+ def rate(self, class_rating: Literal[0, 1, 2, 3], coach_rating: Literal[0, 1, 2, 3]) -> None:
80
+ """Rate the class and coach for this workout.
81
+
82
+ The class rating must be 0, 1, 2, or 3. 0 is the same as dismissing the prompt to rate the class/coach. 1 - 3\
83
+ is a range from bad to good.
84
+
85
+ Args:
86
+ class_rating (Literal[0, 1, 2, 3]): Rating for the class.
87
+ coach_rating (Literal[0, 1, 2, 3]): Rating for the coach.
88
+
89
+ Raises:
90
+ ValueError: If the API instance is not set.
91
+ AlreadyRatedError: If the performance summary is already rated.
92
+ ClassNotRatableError: If the performance summary is not rateable.
93
+ """
94
+ self.raise_if_api_not_set()
95
+ self._api.workouts.rate_class_from_workout(self, class_rating=class_rating, coach_rating=coach_rating)
@@ -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.
@@ -0,0 +1,59 @@
1
+ otf_api/__init__.py,sha256=mpjME4x_GP5hUzbQ99qMPuZ4pzbQNrYXLAXqnGNsXcw,1150
2
+ otf_api/cache.py,sha256=m4xUBhaS0MkcZypon1jjfhJzZPRQBE5Fto4_RMReXZ0,4311
3
+ otf_api/exceptions.py,sha256=b6ZdH1dtYyUfXSupdVGGni6d66qqhzD0SGzyuty89gM,2174
4
+ otf_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ otf_api/api/__init__.py,sha256=YC6bwYiAN6BSE7sqbxr2l34vwSdZPMGDv3wwPIQ_S3o,51
6
+ otf_api/api/_compat.py,sha256=vWMC6kC3tK9qoXwnU5AgbQie-dhYAkkUI-hmq_4nf7Q,3512
7
+ otf_api/api/api.py,sha256=yda8RObOMd_lBWbqlAAUZ3dTwGhdEYsfGQJDiphp6Eo,2633
8
+ otf_api/api/client.py,sha256=o533H-nYweRcosBEYmm3U-7ivkpGDF7jElM9iIhK7E8,8157
9
+ otf_api/api/utils.py,sha256=mNDUrz6d8b8KqTaUr5fGGGejy6bq9HHLs9LS8ibbfW4,9926
10
+ otf_api/api/bookings/__init__.py,sha256=ocHSZXV4nnkZhjpjBX76iKHCQ21_ZL5hV4YKgUR0Wwg,62
11
+ otf_api/api/bookings/booking_api.py,sha256=s4GWT1MYUY0-EjXucj_8rcNfRP6o-sFGfVsmY3RJMdM,22087
12
+ otf_api/api/bookings/booking_client.py,sha256=qYaEomexGWP2N_WdWePMN7BR8aIKEpO71g5J0KaVfAc,4244
13
+ otf_api/api/members/__init__.py,sha256=6mkeMiATRsNQTYQ37P7k6SWf9RZ9T5QY9-_r1sS1-vY,59
14
+ otf_api/api/members/member_api.py,sha256=bpSnUWrLbrqkIcwhzaPonkWTa9-gLPdadzE4ec_meT0,7224
15
+ otf_api/api/members/member_client.py,sha256=d6v6vexmtiedw1-lXLMobbN8tTmRT-zHq7VzdJb5wDE,4528
16
+ otf_api/api/studios/__init__.py,sha256=vPir509hQOJL7uJ4yBCOHgooXVnXs6N5RlWs1tN54Uk,59
17
+ otf_api/api/studios/studio_api.py,sha256=qggdaTGeYtrE1q5oV8ozCqAC5Xv6DtwVG5dHzSB1q74,6781
18
+ otf_api/api/studios/studio_client.py,sha256=KWc4CF-2387WDUFYpMtIUm3KyPj1JdelCn7RH9L1FBI,4707
19
+ otf_api/api/workouts/__init__.py,sha256=GPb2cEsAxoaiJIP8Finrk9x9vUeL9NXA9iz2eya9HYk,62
20
+ otf_api/api/workouts/workout_api.py,sha256=h65nEt2-2mYV33tiVkv2N6L7NoEJ_naViw-IrMnEn8E,13207
21
+ otf_api/api/workouts/workout_client.py,sha256=MvEWbhPXywpQMSdsBSGCYBIR65TJjdqgb7T0Bc8H8D0,6762
22
+ otf_api/auth/__init__.py,sha256=uTvFTNQVvfogTN_-4D3bRSundAf2enUJ5ji6PwO0xm8,104
23
+ otf_api/auth/auth.py,sha256=67-RXDla3rVm5vmlyPEOqPOF5kecJiEznVmq0gSz3M0,16448
24
+ otf_api/auth/user.py,sha256=OChRG0EhZ63vEtT_Sg3NmXWmOD1vZcILuHhRXlwaG0I,2156
25
+ otf_api/auth/utils.py,sha256=YBxqg2h59u4V1ij5kgqJDyKh0gtgOlXrSZdpAKSdelY,3954
26
+ otf_api/models/__init__.py,sha256=P0IjxRUPIfzUI-e3VDMpipJeWr6BlypWAED2ovnZw-w,1545
27
+ otf_api/models/base.py,sha256=KJlIxl_sRj6f-g5vKYPw4yV6fGDk-fwZ93EO0JGPYMw,202
28
+ otf_api/models/mixins.py,sha256=mY9ufoi3QJM5H7KMU9q7grgYuZp85xLs8ie2aLikvi8,4164
29
+ otf_api/models/bookings/__init__.py,sha256=-EtaVW9qF5dmWvSlkq693vkxc3bX6d7dWOgcnNNfpV4,642
30
+ otf_api/models/bookings/bookings.py,sha256=YlocSxdOEECxcCsVkEYAXHFE3EmMTUBxXgSFFxskaLk,5681
31
+ otf_api/models/bookings/bookings_v2.py,sha256=Jd9zh4E3AEWUyxLTISq2j2I_firKCfV5IQzNdkc7iK4,7965
32
+ otf_api/models/bookings/classes.py,sha256=k5cOdMCdDyyXgwBnMrLV9gV_CYE02L8rKewTz8Vv7mg,5068
33
+ otf_api/models/bookings/enums.py,sha256=z9nSciWjvnpKDTHwj496aTKGgREvAeu-h6RKBAf-Tm0,3140
34
+ otf_api/models/bookings/filters.py,sha256=quorUTo9_PR03JMv2m2qhXKhGtb_URLOu92Lj2Y6IMQ,4866
35
+ otf_api/models/bookings/ratings.py,sha256=QHUQQoDmT1sLVil1QJsqP1QKFWfSypg7B2RzyQeCMng,914
36
+ otf_api/models/members/__init__.py,sha256=umzYnnZvRDcX7qVq3t5IRjxxtmTe4F75JjZJm5923gA,199
37
+ otf_api/models/members/member_detail.py,sha256=ACcfomS2hz7i-8521jUBRGYO3oTwpMMrIP9wcTHFOgY,8039
38
+ otf_api/models/members/member_membership.py,sha256=pz-lHD6T60-rh1StRlCJd0L-g3PPkiFIeRVv1P-ebjY,1481
39
+ otf_api/models/members/member_purchases.py,sha256=oEHMRJCFeU8A39fcY0RDDUimedP4aLjh4Ueoz5te0eo,1775
40
+ otf_api/models/members/notifications.py,sha256=7RONios8H1WnzRKuy3E7s5j6MXSwKk60b5NkjB6Biok,910
41
+ otf_api/models/studios/__init__.py,sha256=2mMRnwFdlnE6Mubcrh7K35x86jEINGnSPa7oYHETN8Q,210
42
+ otf_api/models/studios/enums.py,sha256=I5B4iyHUvcBfn0C3pMwnriAZxx8-fhupDlyrD_jSZa8,258
43
+ otf_api/models/studios/studio_detail.py,sha256=cinktnXq3AH0BnSR1W4kXrIkDw8bg0IOXO4_sS1FCHc,4979
44
+ otf_api/models/studios/studio_services.py,sha256=Y7INy_9-UhIM1g5EQjmhlJtdJouSvYIvG8GIa4H3UpU,1835
45
+ otf_api/models/workouts/__init__.py,sha256=picQOBsx0JhkgQlZwQq_vQShC5ZepJWpvSTO3N1K_RA,1003
46
+ otf_api/models/workouts/body_composition_list.py,sha256=E5xDqP647QEC8seKBBDeFaTod-mL4alXn-tP83RtvIg,14930
47
+ otf_api/models/workouts/challenge_tracker_content.py,sha256=kP57mYsLiDfuSVRJswESi-XXCL7BBQfULEXEiOM78SI,2459
48
+ otf_api/models/workouts/challenge_tracker_detail.py,sha256=v-1WbOnBtuUyxllxlzUSuhtcBr3hV8PldI_6-SZCPQI,4920
49
+ otf_api/models/workouts/enums.py,sha256=El_H3l6XLxO27Xo23YheR6LPvIjfTUaeK0rBpQvRHpc,1540
50
+ otf_api/models/workouts/lifetime_stats.py,sha256=WBDqdcJ4Bz2YXg1WOijeJgs7LKcix_JgZ5lkwsOpGiI,3819
51
+ otf_api/models/workouts/out_of_studio_workout_history.py,sha256=-BKp-MnIp_5-U2KWy6nv58H6flgcbdOGsSbz5cHq20w,1945
52
+ otf_api/models/workouts/performance_summary.py,sha256=R1p-g1L4XWWEiBYaGtX5JFvoS3Z6bzQrsPIfTQHDqyM,3464
53
+ otf_api/models/workouts/telemetry.py,sha256=rKsmLbsOVUxUXH439zxxIEWYGTohKNemqrtMamAl5cA,3465
54
+ otf_api/models/workouts/workout.py,sha256=_KIR9fLI4LgUlS02Q5SNauHd4aOqJI9H4uaFKf-xJOU,4580
55
+ otf_api-0.13.0.dist-info/licenses/LICENSE,sha256=UaPT9ynYigC3nX8n22_rC37n-qmTRKLFaHrtUwF9ktE,1071
56
+ otf_api-0.13.0.dist-info/METADATA,sha256=-x6Y4z5eG5ECRWfbS_jmbFr3oo6eNrKdrNMeDkMniuM,2162
57
+ otf_api-0.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
58
+ otf_api-0.13.0.dist-info/top_level.txt,sha256=KAhYg1X2YG0LkTuVRhUV1I_AReNZUVNdEan7cp0pEE4,8
59
+ otf_api-0.13.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5