sweatstack 0.37.0__tar.gz → 0.40.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.
- {sweatstack-0.37.0 → sweatstack-0.40.0}/PKG-INFO +1 -1
- {sweatstack-0.37.0 → sweatstack-0.40.0}/pyproject.toml +1 -1
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/client.py +13 -14
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/openapi_schemas.py +8 -1
- sweatstack-0.40.0/src/sweatstack/schemas.py +104 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/utils.py +1 -26
- sweatstack-0.37.0/src/sweatstack/schemas.py +0 -5
- {sweatstack-0.37.0 → sweatstack-0.40.0}/.gitignore +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/.python-version +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/DEVELOPMENT.md +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/Makefile +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/README.md +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/playground/.ipynb_checkpoints/Untitled-checkpoint.ipynb +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/playground/README.md +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/playground/Sweat Stack examples/Getting started.ipynb +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/playground/Untitled.ipynb +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/playground/hello.py +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/playground/pyproject.toml +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/Sweat Stack examples/Getting started.ipynb +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/__init__.py +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/cli.py +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/constants.py +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/ipython_init.py +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/jupyterlab_oauth2_startup.py +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/py.typed +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/streamlit.py +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/sweatshell.py +0 -0
- {sweatstack-0.37.0 → sweatstack-0.40.0}/uv.lock +0 -0
|
@@ -444,7 +444,7 @@ class Client(OAuth2Mixin, DelegationMixin):
|
|
|
444
444
|
tags: list[str] | None = None,
|
|
445
445
|
limit: int = 100,
|
|
446
446
|
as_dataframe: bool = False,
|
|
447
|
-
) ->
|
|
447
|
+
) -> list[ActivitySummary] | pd.DataFrame:
|
|
448
448
|
"""Gets a list of activities based on specified filters.
|
|
449
449
|
|
|
450
450
|
Args:
|
|
@@ -456,28 +456,27 @@ class Client(OAuth2Mixin, DelegationMixin):
|
|
|
456
456
|
as_dataframe: Whether to return results as a pandas DataFrame. Defaults to False.
|
|
457
457
|
|
|
458
458
|
Returns:
|
|
459
|
-
Either a
|
|
459
|
+
Either a list of ActivitySummary objects or a pandas DataFrame containing
|
|
460
460
|
the activities data, depending on the value of as_dataframe.
|
|
461
461
|
|
|
462
462
|
Raises:
|
|
463
463
|
HTTPStatusError: If the API request fails.
|
|
464
464
|
"""
|
|
465
|
-
|
|
465
|
+
activities = list(self._get_activities_generator(
|
|
466
466
|
start=start,
|
|
467
467
|
end=end,
|
|
468
468
|
sports=sports,
|
|
469
469
|
tags=tags,
|
|
470
470
|
limit=limit,
|
|
471
|
-
)
|
|
471
|
+
))
|
|
472
472
|
if as_dataframe:
|
|
473
|
-
df = pd.DataFrame([activity.model_dump() for activity in
|
|
474
|
-
df = df.set_index(df["start"].rename("timestamp"))
|
|
473
|
+
df = pd.DataFrame([activity.model_dump() for activity in activities])
|
|
475
474
|
df = self._normalize_dataframe_column(df, "summary")
|
|
476
475
|
df = self._normalize_dataframe_column(df, "laps")
|
|
477
476
|
df = self._normalize_dataframe_column(df, "traces")
|
|
478
477
|
return self._postprocess_dataframe(df)
|
|
479
478
|
else:
|
|
480
|
-
return
|
|
479
|
+
return activities
|
|
481
480
|
|
|
482
481
|
def get_latest_activity(
|
|
483
482
|
self,
|
|
@@ -502,7 +501,7 @@ class Client(OAuth2Mixin, DelegationMixin):
|
|
|
502
501
|
StopIteration: If no activities match the filters.
|
|
503
502
|
HTTPStatusError: If the API request fails.
|
|
504
503
|
"""
|
|
505
|
-
return next(self.
|
|
504
|
+
return next(self._get_activities_generator(
|
|
506
505
|
start=start,
|
|
507
506
|
end=end,
|
|
508
507
|
sports=[sport] if sport is not None else None,
|
|
@@ -841,7 +840,7 @@ class Client(OAuth2Mixin, DelegationMixin):
|
|
|
841
840
|
tags: list[str] | None = None,
|
|
842
841
|
limit: int = 100,
|
|
843
842
|
as_dataframe: bool = False,
|
|
844
|
-
) ->
|
|
843
|
+
) -> list[TraceDetails] | pd.DataFrame:
|
|
845
844
|
"""Gets a list of traces based on specified filters.
|
|
846
845
|
|
|
847
846
|
Args:
|
|
@@ -853,23 +852,23 @@ class Client(OAuth2Mixin, DelegationMixin):
|
|
|
853
852
|
as_dataframe: Whether to return results as a pandas DataFrame. Defaults to False.
|
|
854
853
|
|
|
855
854
|
Returns:
|
|
856
|
-
Either a
|
|
855
|
+
Either a list of TraceDetails objects or a pandas DataFrame containing
|
|
857
856
|
the traces data, depending on the value of as_dataframe.
|
|
858
857
|
|
|
859
858
|
Raises:
|
|
860
859
|
HTTPStatusError: If the API request fails.
|
|
861
860
|
"""
|
|
862
|
-
|
|
861
|
+
traces = list(self._get_traces_generator(
|
|
863
862
|
start=start,
|
|
864
863
|
end=end,
|
|
865
864
|
sports=sports,
|
|
866
865
|
tags=tags,
|
|
867
866
|
limit=limit,
|
|
868
|
-
)
|
|
867
|
+
))
|
|
869
868
|
if not as_dataframe:
|
|
870
|
-
return
|
|
869
|
+
return traces
|
|
871
870
|
|
|
872
|
-
data = pd.DataFrame([trace.model_dump() for trace in
|
|
871
|
+
data = pd.DataFrame([trace.model_dump() for trace in traces])
|
|
873
872
|
|
|
874
873
|
if "activity" in data.columns:
|
|
875
874
|
data = self._normalize_dataframe_column(data, "activity")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# generated by datamodel-codegen:
|
|
2
2
|
# filename: openapi.json
|
|
3
|
-
# timestamp: 2025-04-
|
|
3
|
+
# timestamp: 2025-04-11T08:42:56+00:00
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
@@ -332,6 +332,8 @@ class Lap(BaseModel):
|
|
|
332
332
|
start: datetime = Field(..., title='Start')
|
|
333
333
|
end: datetime = Field(..., title='End')
|
|
334
334
|
duration: timedelta = Field(..., title='Duration')
|
|
335
|
+
start_local: datetime = Field(..., title='Start Local')
|
|
336
|
+
end_local: datetime = Field(..., title='End Local')
|
|
335
337
|
|
|
336
338
|
|
|
337
339
|
class ActivityDetails(BaseModel):
|
|
@@ -347,6 +349,8 @@ class ActivityDetails(BaseModel):
|
|
|
347
349
|
traces: Optional[List[TraceDetails]] = Field(None, title='Traces')
|
|
348
350
|
distance: Optional[float] = Field(None, title='Distance')
|
|
349
351
|
duration: timedelta = Field(..., title='Duration')
|
|
352
|
+
start_local: datetime = Field(..., title='Start Local')
|
|
353
|
+
end_local: datetime = Field(..., title='End Local')
|
|
350
354
|
|
|
351
355
|
|
|
352
356
|
class ActivitySummary(BaseModel):
|
|
@@ -361,6 +365,8 @@ class ActivitySummary(BaseModel):
|
|
|
361
365
|
laps: Optional[List[Lap]] = Field(None, title='Laps')
|
|
362
366
|
traces: Optional[List[TraceDetails]] = Field(None, title='Traces')
|
|
363
367
|
duration: timedelta = Field(..., title='Duration')
|
|
368
|
+
start_local: datetime = Field(..., title='Start Local')
|
|
369
|
+
end_local: datetime = Field(..., title='End Local')
|
|
364
370
|
|
|
365
371
|
|
|
366
372
|
class TraceDetails(BaseModel):
|
|
@@ -376,6 +382,7 @@ class TraceDetails(BaseModel):
|
|
|
376
382
|
lap: Optional[Lap] = None
|
|
377
383
|
activity: Optional[ActivitySummary] = None
|
|
378
384
|
sport: Optional[Sport] = None
|
|
385
|
+
timestamp_local: datetime = Field(..., title='Timestamp Local')
|
|
379
386
|
|
|
380
387
|
|
|
381
388
|
ActivityDetails.model_rebuild()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List, Union
|
|
3
|
+
|
|
4
|
+
from .openapi_schemas import (
|
|
5
|
+
ActivityDetails, ActivitySummary, Metric, Scope, Sport,
|
|
6
|
+
TraceDetails, UserInfoResponse, UserSummary
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parent_sport(sport: Sport) -> Sport:
|
|
11
|
+
"""Returns the parent sport of a given sport.
|
|
12
|
+
|
|
13
|
+
For sports with a hierarchical structure (e.g., 'cycling.road'), returns the parent sport
|
|
14
|
+
('cycling'). If the sport has no parent (is already a root sport), returns the sport itself.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
sport (Sport): The sport enum value to find the parent of.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Sport: The parent sport enum value, or the original sport if it has no parent.
|
|
21
|
+
"""
|
|
22
|
+
parts = sport.value.split(".")
|
|
23
|
+
if len(parts) == 1:
|
|
24
|
+
return sport
|
|
25
|
+
return sport.__class__(".".join(parts[:-1]))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def root_sport(sport: Sport) -> Sport:
|
|
29
|
+
"""Returns the root sport of a given sport.
|
|
30
|
+
|
|
31
|
+
For sports with a hierarchical structure (e.g., 'cycling.road' or 'cycling.road.gravel'),
|
|
32
|
+
returns the root sport ('cycling'). If the sport is already a root sport, returns the sport itself.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
sport (Sport): The sport enum value to find the root of.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Sport: The root sport enum value.
|
|
39
|
+
"""
|
|
40
|
+
return sport.__class__(sport.value.split(".")[0])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_root_sport(sport: Sport) -> bool:
|
|
44
|
+
"""Determines if a sport is a root sport.
|
|
45
|
+
|
|
46
|
+
A root sport is one that doesn't have a parent sport in the hierarchy
|
|
47
|
+
(e.g., 'cycling' is a root sport, while 'cycling.road' is not).
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
sport (Sport): The sport enum value to check.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
bool: True if the sport is a root sport, False otherwise.
|
|
54
|
+
"""
|
|
55
|
+
return sport == root_sport(sport)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_sub_sport_of(sport: Sport, sport_or_sports: Union[Sport, List[Sport]]) -> bool:
|
|
59
|
+
"""Determines if a sport is a sub-sport of another sport or list of sports.
|
|
60
|
+
|
|
61
|
+
For example, 'cycling.road' is a sub-sport of 'cycling', but not of 'running'.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
sport (Sport): The sport to check.
|
|
65
|
+
sport_or_sports (Union[Sport, List[Sport]]): A sport or list of sports to check against.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
bool: True if the sport is a sub-sport of any of the provided sports, False otherwise.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
ValueError: If sport_or_sports is not a Sport or a list of Sports.
|
|
72
|
+
"""
|
|
73
|
+
if isinstance(sport_or_sports, Sport):
|
|
74
|
+
return sport.value.startswith(sport_or_sports.value)
|
|
75
|
+
elif isinstance(sport_or_sports, (list, tuple)):
|
|
76
|
+
return any(is_sub_sport_of(sport, sport) for sport in sport_or_sports)
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError(f"Invalid type for sport_or_sports: {type(sport_or_sports)}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def display_name(sport: Sport) -> str:
|
|
82
|
+
"""Returns a human-readable display name for a sport.
|
|
83
|
+
|
|
84
|
+
This function converts a Sport enum value into a formatted string suitable for display.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
sport (Sport): The sport enum value to format.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
str: A human-readable display name for the sport.
|
|
91
|
+
"""
|
|
92
|
+
parts = sport.value.split(".")
|
|
93
|
+
base_sport = parts[0]
|
|
94
|
+
base_sport = base_sport.replace("_", " ")
|
|
95
|
+
if len(parts) == 1:
|
|
96
|
+
return base_sport
|
|
97
|
+
the_rest = " ".join(parts[1:]).replace("_", " ")
|
|
98
|
+
return f"{base_sport} ({the_rest})"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
Sport.root_sport = root_sport
|
|
102
|
+
Sport.parent_sport = parent_sport
|
|
103
|
+
Sport.is_sub_sport_of = is_sub_sport_of
|
|
104
|
+
Sport.display_name = display_name
|
|
@@ -52,29 +52,4 @@ def make_dataframe_streamlit_compatible(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
52
52
|
lambda x: x.value if isinstance(x, Enum) else x
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
-
return df_copy if df_copy is not None else df
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def format_sport(sport: Sport):
|
|
59
|
-
"""Formats a sport enum value into a human-readable string.
|
|
60
|
-
|
|
61
|
-
This function takes a Sport enum and converts it to a formatted string representation.
|
|
62
|
-
For example, "cycling.road" becomes "cycling (road)" and "running" remains "running".
|
|
63
|
-
Underscores in sport names are replaced with spaces.
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
sport: A Sport enum value to format.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
str: A human-readable formatted string representation of the sport.
|
|
70
|
-
"""
|
|
71
|
-
parts = sport.value.split(".")
|
|
72
|
-
base_sport = parts[0]
|
|
73
|
-
base_sport = base_sport.replace("_", " ")
|
|
74
|
-
|
|
75
|
-
if len(parts) == 1:
|
|
76
|
-
return base_sport
|
|
77
|
-
|
|
78
|
-
remainder = " ".join(parts[1:]).replace("_", " ")
|
|
79
|
-
|
|
80
|
-
return f"{base_sport} ({remainder})"
|
|
55
|
+
return df_copy if df_copy is not None else df
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sweatstack-0.37.0 → sweatstack-0.40.0}/playground/.ipynb_checkpoints/Untitled-checkpoint.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
{sweatstack-0.37.0 → sweatstack-0.40.0}/playground/Sweat Stack examples/Getting started.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sweatstack-0.37.0 → sweatstack-0.40.0}/src/sweatstack/Sweat Stack examples/Getting started.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|