sweatstack 0.57.0__tar.gz → 0.59.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 (33) hide show
  1. {sweatstack-0.57.0 → sweatstack-0.59.0}/.claude/settings.local.json +5 -1
  2. {sweatstack-0.57.0 → sweatstack-0.59.0}/CHANGELOG.md +13 -1
  3. {sweatstack-0.57.0 → sweatstack-0.59.0}/PKG-INFO +1 -1
  4. sweatstack-0.59.0/docs/index.rst +7 -0
  5. {sweatstack-0.57.0 → sweatstack-0.59.0}/pyproject.toml +1 -1
  6. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/openapi_schemas.py +135 -41
  7. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/schemas.py +46 -1
  8. {sweatstack-0.57.0 → sweatstack-0.59.0}/uv.lock +1 -1
  9. sweatstack-0.57.0/docs/index.rst +0 -0
  10. {sweatstack-0.57.0 → sweatstack-0.59.0}/.gitignore +0 -0
  11. {sweatstack-0.57.0 → sweatstack-0.59.0}/.python-version +0 -0
  12. {sweatstack-0.57.0 → sweatstack-0.59.0}/DEVELOPMENT.md +0 -0
  13. {sweatstack-0.57.0 → sweatstack-0.59.0}/Makefile +0 -0
  14. {sweatstack-0.57.0 → sweatstack-0.59.0}/README.md +0 -0
  15. {sweatstack-0.57.0 → sweatstack-0.59.0}/docs/conf.py +0 -0
  16. {sweatstack-0.57.0 → sweatstack-0.59.0}/docs/everything.rst +0 -0
  17. {sweatstack-0.57.0 → sweatstack-0.59.0}/playground/.ipynb_checkpoints/Untitled-checkpoint.ipynb +0 -0
  18. {sweatstack-0.57.0 → sweatstack-0.59.0}/playground/README.md +0 -0
  19. {sweatstack-0.57.0 → sweatstack-0.59.0}/playground/Sweat Stack examples/Getting started.ipynb +0 -0
  20. {sweatstack-0.57.0 → sweatstack-0.59.0}/playground/Untitled.ipynb +0 -0
  21. {sweatstack-0.57.0 → sweatstack-0.59.0}/playground/hello.py +0 -0
  22. {sweatstack-0.57.0 → sweatstack-0.59.0}/playground/pyproject.toml +0 -0
  23. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/Sweat Stack examples/Getting started.ipynb +0 -0
  24. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/__init__.py +0 -0
  25. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/cli.py +0 -0
  26. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/client.py +0 -0
  27. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/constants.py +0 -0
  28. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/ipython_init.py +0 -0
  29. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/jupyterlab_oauth2_startup.py +0 -0
  30. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/py.typed +0 -0
  31. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/streamlit.py +0 -0
  32. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/sweatshell.py +0 -0
  33. {sweatstack-0.57.0 → sweatstack-0.59.0}/src/sweatstack/utils.py +0 -0
@@ -3,7 +3,11 @@
3
3
  "allow": [
4
4
  "Bash(python:*)",
5
5
  "WebFetch(domain:app.sweatstack.no)",
6
- "Bash(cat:*)"
6
+ "Bash(cat:*)",
7
+ "Bash(find:*)",
8
+ "Bash(grep:*)",
9
+ "Bash(uv run python:*)",
10
+ "Bash(uv run pytest:*)"
7
11
  ],
8
12
  "deny": []
9
13
  }
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
+ ## [0.59.0] - 2026-01-27
10
+
11
+ ### Changed
12
+ - Future-proofed response enums.
13
+
14
+
15
+ ## [0.58.0] - 2026-01-24
16
+
17
+ ### Fixed
18
+ - Fixes missing altitude metric.
19
+
20
+
9
21
  ## [0.57.0] - 2025-12-04
10
22
 
11
23
  ### Added
@@ -110,4 +122,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
110
122
 
111
123
  ## Changed
112
124
 
113
- - The `sweatlab` and `sweatshell` commands now use the new `ss.authenticate()` method.
125
+ - The `sweatlab` and `sweatshell` commands now use the new `ss.authenticate()` method.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sweatstack
3
- Version: 0.57.0
3
+ Version: 0.59.0
4
4
  Summary: The official Python client for SweatStack
5
5
  Author-email: Aart Goossens <aart@gssns.io>
6
6
  Requires-Python: >=3.9
@@ -0,0 +1,7 @@
1
+ MyPackage Documentation
2
+ =======================
3
+
4
+ .. toctree::
5
+ :maxdepth: 2
6
+
7
+ everything
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sweatstack"
3
- version = "0.57.0"
3
+ version = "0.59.0"
4
4
  description = "The official Python client for SweatStack"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: openapi.json
3
- # timestamp: 2025-08-07T11:42:52+00:00
3
+ # timestamp: 2026-01-24T14:09:07+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -22,13 +22,33 @@ from pydantic import (
22
22
 
23
23
  class Activity(BaseModel):
24
24
  id: str = Field(..., title='Id')
25
- start_date_local: datetime = Field(..., title='Start Date Local')
25
+ start_date_local: Optional[datetime] = Field(None, title='Start Date Local')
26
26
 
27
27
 
28
28
  class ActivityUpdate(BaseModel):
29
29
  tags: Optional[List[str]] = Field(None, title='Tags')
30
30
 
31
31
 
32
+ class AltitudeSummary(BaseModel):
33
+ mean: Optional[float] = Field(None, title='Mean')
34
+ gain: Optional[Union[int, float]] = Field(None, title='Gain')
35
+ loss: Optional[Union[int, float]] = Field(None, title='Loss')
36
+ min: Optional[Union[int, float]] = Field(None, title='Min')
37
+ max: Optional[Union[int, float]] = Field(None, title='Max')
38
+
39
+
40
+ class ApplicationCreateOrUpdate(BaseModel):
41
+ name: str = Field(..., title='Name')
42
+ description: Optional[str] = Field(None, title='Description')
43
+ url: Optional[AnyUrl] = Field(None, title='Url')
44
+ image: Optional[AnyUrl] = Field(None, title='Image')
45
+ redirect_uris: Optional[List[AnyUrl]] = Field(None, title='Redirect Uris')
46
+ privacy_statement: Optional[AnyUrl] = Field(None, title='Privacy Statement')
47
+ published: Optional[bool] = Field(False, title='Published')
48
+ webhook_endpoints: Optional[List[AnyUrl]] = Field(None, title='Webhook Endpoints')
49
+ webhook_secret: Optional[SecretStr] = Field(None, title='Webhook Secret')
50
+
51
+
32
52
  class Prompt(Enum):
33
53
  none = 'none'
34
54
  login = 'login'
@@ -36,6 +56,10 @@ class Prompt(Enum):
36
56
  select_account = 'select_account'
37
57
 
38
58
 
59
+ class BackfillError(BaseModel):
60
+ error: str = Field(..., title='Error')
61
+
62
+
39
63
  class BackfillStatus(BaseModel):
40
64
  backfill_loaded_until: Optional[datetime] = Field(
41
65
  ..., title='Backfill Loaded Until'
@@ -46,16 +70,6 @@ class BodyAddEmailPartialsAddEmailPost(BaseModel):
46
70
  email: EmailStr = Field(..., title='Email')
47
71
 
48
72
 
49
- class BodyCreateApplicationApplicationsPost(BaseModel):
50
- name: str = Field(..., title='Name')
51
- description: str = Field(..., title='Description')
52
- url: AnyUrl = Field(..., title='Url')
53
- image: AnyUrl = Field(..., title='Image')
54
- redirect_uris: List[str] = Field(..., title='Redirect Uris')
55
- privacy_statement: AnyUrl = Field(..., title='Privacy Statement')
56
- published: Optional[bool] = Field(False, title='Published')
57
-
58
-
59
73
  class BodyCreateApplicationSecretApplicationsApplicationIdSecretsPost(BaseModel):
60
74
  label: str = Field(..., title='Label')
61
75
 
@@ -668,15 +682,23 @@ class Tz(Enum):
668
682
 
669
683
  class BodyLoginPostLoginPost(BaseModel):
670
684
  email: EmailStr = Field(..., title='Email')
671
- password: SecretStr = Field(..., title='Password')
672
- tz: Tz = Field(..., title='Tz')
685
+ tz: Optional[Tz] = Field('Europe/Oslo', title='Tz')
673
686
  state: Optional[str] = Field(None, title='State')
674
687
 
675
688
 
676
- class BodyRegisterPostRegisterPost(BaseModel):
689
+ class BodyPostCreateManagedUserSettingsManagedUsersPost(BaseModel):
690
+ first_name: str = Field(..., title='First Name')
691
+ last_name: Optional[str] = Field(None, title='Last Name')
692
+
693
+
694
+ class BodyPutUpdateManagedUserSettingsManagedUsersUserIdPut(BaseModel):
695
+ first_name: str = Field(..., title='First Name')
696
+ last_name: Optional[str] = Field(None, title='Last Name')
697
+
698
+
699
+ class BodyResendMagicLinkPartialsResendMagicLinkPost(BaseModel):
677
700
  email: EmailStr = Field(..., title='Email')
678
- password: SecretStr = Field(..., title='Password')
679
- tz: Tz = Field(..., title='Tz')
701
+ tz: Optional[Tz] = Field('Europe/Oslo', title='Tz')
680
702
 
681
703
 
682
704
  class BodySaveOrUpdateIntegrationProviderTenantsTenantIdIntegrationProvidersIntegrationNamePost(
@@ -687,19 +709,9 @@ class BodySaveOrUpdateIntegrationProviderTenantsTenantIdIntegrationProvidersInte
687
709
  redirect_url: str = Field(..., title='Redirect Url')
688
710
 
689
711
 
690
- class BodySendVerificationEmailPartialsSendVerificationEmailPost(BaseModel):
691
- email: EmailStr = Field(..., title='Email')
692
- tz: Tz = Field(..., title='Tz')
693
-
694
-
695
- class BodyUpdateApplicationApplicationsApplicationIdPut(BaseModel):
696
- name: str = Field(..., title='Name')
697
- description: str = Field(..., title='Description')
698
- url: AnyUrl = Field(..., title='Url')
699
- image: AnyUrl = Field(..., title='Image')
700
- redirect_uris: List[str] = Field(..., title='Redirect Uris')
701
- privacy_statement: AnyUrl = Field(..., title='Privacy Statement')
702
- published: Optional[bool] = Field(False, title='Published')
712
+ class BodyUpdateManagedUserApiV1UsersUserIdPut(BaseModel):
713
+ first_name: Optional[str] = Field(None, title='First Name')
714
+ last_name: Optional[str] = Field(None, title='Last Name')
703
715
 
704
716
 
705
717
  class BodyUpdateUserUsersUserIdPut(BaseModel):
@@ -715,6 +727,11 @@ class BodyUploadActivityFileDataUploadPost(BaseModel):
715
727
  files: List[bytes] = Field(..., title='Files')
716
728
 
717
729
 
730
+ class CadenceSummary(BaseModel):
731
+ mean: Optional[float] = Field(None, title='Mean')
732
+ max: Optional[float] = Field(None, title='Max')
733
+
734
+
718
735
  class CoreTemperatureSummary(BaseModel):
719
736
  mean: Optional[float] = Field(None, title='Mean')
720
737
  min: Optional[float] = Field(None, title='Min')
@@ -724,12 +741,7 @@ class CoreTemperatureSummary(BaseModel):
724
741
 
725
742
 
726
743
  class DistanceSummary(BaseModel):
727
- sum: Optional[int] = Field(None, title='Sum')
728
-
729
-
730
- class ElevationSummary(BaseModel):
731
- min: Optional[int] = Field(None, title='Min')
732
- max: Optional[int] = Field(None, title='Max')
744
+ sum: Optional[Union[int, float]] = Field(None, title='Sum')
733
745
 
734
746
 
735
747
  class GarminBackfillBody(BaseModel):
@@ -739,6 +751,11 @@ class GarminBackfillBody(BaseModel):
739
751
  user_id__not_in: Optional[List[str]] = Field(None, title='User Id Not In')
740
752
 
741
753
 
754
+ class GarminConnectUserPermission(Enum):
755
+ HISTORICAL_DATA_EXPORT = 'HISTORICAL_DATA_EXPORT'
756
+ ACTIVITY_EXPORT = 'ACTIVITY_EXPORT'
757
+
758
+
742
759
  class GarminDeregistration(BaseModel):
743
760
  userId: str = Field(..., title='Userid')
744
761
  userAccessToken: str = Field(..., title='Useraccesstoken')
@@ -754,6 +771,18 @@ class GarminFileTypes(Enum):
754
771
  TCX = 'TCX'
755
772
 
756
773
 
774
+ class GarminUserPermissionChange(BaseModel):
775
+ userId: str = Field(..., title='Userid')
776
+ userAccessToken: str = Field(..., title='Useraccesstoken')
777
+ permissions: List[GarminConnectUserPermission] = Field(..., title='Permissions')
778
+
779
+
780
+ class GarminUserPermissionsBody(BaseModel):
781
+ userPermissionsChange: List[GarminUserPermissionChange] = Field(
782
+ ..., title='Userpermissionschange'
783
+ )
784
+
785
+
757
786
  class GrantType(Enum):
758
787
  authorization_code = 'authorization_code'
759
788
  refresh_token = 'refresh_token'
@@ -796,6 +825,19 @@ class IntervalsIcuWebhookEventType(Enum):
796
825
  FITNESS_UPDATED = 'FITNESS_UPDATED'
797
826
 
798
827
 
828
+ class ManagedUserCreate(BaseModel):
829
+ first_name: str = Field(..., title='First Name')
830
+ last_name: Optional[str] = Field(..., title='Last Name')
831
+
832
+
833
+ class MetabolicMapResponse(BaseModel):
834
+ fatmax: float = Field(..., title='Fatmax')
835
+ mlss: float = Field(..., title='Mlss')
836
+ cp: float = Field(..., title='Cp')
837
+ vo2max: float = Field(..., title='Vo2Max')
838
+ vo2max_intensity: float = Field(..., title='Vo2Max Intensity')
839
+
840
+
799
841
  class Metric(Enum):
800
842
  duration = 'duration'
801
843
  lap = 'lap'
@@ -806,6 +848,7 @@ class Metric(Enum):
806
848
  smo2 = 'smo2'
807
849
  core_temperature = 'core_temperature'
808
850
  elevation = 'elevation'
851
+ altitude = 'altitude'
809
852
  cadence = 'cadence'
810
853
  temperature = 'temperature'
811
854
  distance = 'distance'
@@ -818,7 +861,6 @@ class Metric(Enum):
818
861
 
819
862
  class PowerSummary(BaseModel):
820
863
  mean: Optional[float] = Field(None, title='Mean')
821
- min: Optional[float] = Field(None, title='Min')
822
864
  max: Optional[float] = Field(None, title='Max')
823
865
 
824
866
 
@@ -850,7 +892,6 @@ class Smo2Summary(BaseModel):
850
892
 
851
893
  class SpeedSummary(BaseModel):
852
894
  mean: Optional[float] = Field(None, title='Mean')
853
- min: Optional[float] = Field(None, title='Min')
854
895
  max: Optional[float] = Field(None, title='Max')
855
896
 
856
897
 
@@ -897,10 +938,28 @@ class Sport(Enum):
897
938
  unknown = 'unknown'
898
939
 
899
940
 
941
+ class SubscriptionPlan(Enum):
942
+ free = 'free'
943
+ early_access = 'early_access'
944
+ individual = 'individual'
945
+ developer = 'developer'
946
+ team = 'team'
947
+
948
+
949
+ class TeamCreateOrUpdate(BaseModel):
950
+ name: str = Field(..., title='Name')
951
+ description: str = Field(..., title='Description')
952
+ url: AnyUrl = Field(..., title='Url')
953
+ image: AnyUrl = Field(..., title='Image')
954
+ privacy_statement: AnyUrl = Field(..., title='Privacy Statement')
955
+
956
+
900
957
  class TemperatureSummary(BaseModel):
901
958
  mean: Optional[float] = Field(None, title='Mean')
902
959
  min: Optional[float] = Field(None, title='Min')
903
960
  max: Optional[float] = Field(None, title='Max')
961
+ start: Optional[float] = Field(None, title='Start')
962
+ end: Optional[float] = Field(None, title='End')
904
963
 
905
964
 
906
965
  class TemplateDay(BaseModel):
@@ -965,12 +1024,23 @@ class UserInfoResponse(BaseModel):
965
1024
  name: str = Field(..., title='Name')
966
1025
 
967
1026
 
1027
+ class UserResponse(BaseModel):
1028
+ id: str = Field(..., title='Id')
1029
+ first_name: Optional[str] = Field(..., title='First Name')
1030
+ last_name: Optional[str] = Field(..., title='Last Name')
1031
+ admin: bool = Field(..., title='Admin')
1032
+ registered_at: datetime = Field(..., title='Registered At')
1033
+ display_name: str = Field(..., title='Display Name')
1034
+ is_managed: bool = Field(..., title='Is Managed')
1035
+
1036
+
968
1037
  class UserSummary(BaseModel):
969
1038
  id: str = Field(..., title='Id')
970
1039
  first_name: Optional[str] = Field(..., title='First Name')
971
1040
  last_name: Optional[str] = Field(..., title='Last Name')
972
1041
  scopes: List[Scope] = Field(..., title='Scopes')
973
1042
  display_name: str = Field(..., title='Display Name')
1043
+ is_managed: bool = Field(..., title='Is Managed')
974
1044
 
975
1045
 
976
1046
  class ValidationError(BaseModel):
@@ -990,12 +1060,19 @@ class VolumeQuantity(Enum):
990
1060
  distance = 'distance'
991
1061
 
992
1062
 
1063
+ class WebhookEventType(Enum):
1064
+ activity_created = 'activity_created'
1065
+ activity_updated = 'activity_updated'
1066
+ activity_deleted = 'activity_deleted'
1067
+
1068
+
993
1069
  class ActivitySummarySummary(BaseModel):
994
1070
  power: Optional[PowerSummary] = None
995
1071
  speed: Optional[SpeedSummary] = None
996
1072
  distance: Optional[DistanceSummary] = None
997
- elevation: Optional[ElevationSummary] = None
1073
+ altitude: Optional[AltitudeSummary] = None
998
1074
  heart_rate: Optional[HeartRateSummary] = None
1075
+ cadence: Optional[CadenceSummary] = None
999
1076
  temperature: Optional[TemperatureSummary] = None
1000
1077
  core_temperature: Optional[CoreTemperatureSummary] = None
1001
1078
  smo2: Optional[Smo2Summary] = None
@@ -1013,6 +1090,10 @@ class AuthorizeQueryParams(BaseModel):
1013
1090
  user_flow: Optional[UserFlow] = 'session'
1014
1091
 
1015
1092
 
1093
+ class AuthorizeTeamRequest(BaseModel):
1094
+ scopes: List[Scope] = Field(..., title='Scopes')
1095
+
1096
+
1016
1097
  class BodyAuthorizeOauthAuthorizePost(BaseModel):
1017
1098
  client_id: str = Field(..., title='Client Id')
1018
1099
  redirect_uri: Optional[str] = Field(None, title='Redirect Uri')
@@ -1023,6 +1104,10 @@ class BodyAuthorizeOauthAuthorizePost(BaseModel):
1023
1104
  code_challenge_method: Optional[str] = Field(None, title='Code Challenge Method')
1024
1105
 
1025
1106
 
1107
+ class BodyCreateCheckoutSessionApiV1PaymentStripeCheckoutPost(BaseModel):
1108
+ subscription_plan: SubscriptionPlan
1109
+
1110
+
1026
1111
  class BodyExpressAddEmailPostExpressAddEmailPost(BaseModel):
1027
1112
  email: EmailStr = Field(..., title='Email')
1028
1113
  state: Optional[str] = Field(None, title='State')
@@ -1062,7 +1147,7 @@ class GarminActivityFileData(BaseModel):
1062
1147
  callbackURL: str = Field(..., title='Callbackurl')
1063
1148
  startTimeInSeconds: datetime = Field(..., title='Starttimeinseconds')
1064
1149
  activityId: str = Field(..., title='Activityid')
1065
- activityName: str = Field(..., title='Activityname')
1150
+ activityName: Optional[str] = Field(None, title='Activityname')
1066
1151
  manual: Optional[bool] = Field(False, title='Manual')
1067
1152
 
1068
1153
 
@@ -1085,8 +1170,9 @@ class Lap(BaseModel):
1085
1170
  power: Optional[PowerSummary] = None
1086
1171
  speed: Optional[SpeedSummary] = None
1087
1172
  distance: Optional[DistanceSummary] = None
1088
- elevation: Optional[ElevationSummary] = None
1173
+ altitude: Optional[AltitudeSummary] = None
1089
1174
  heart_rate: Optional[HeartRateSummary] = None
1175
+ cadence: Optional[CadenceSummary] = None
1090
1176
  temperature: Optional[TemperatureSummary] = None
1091
1177
  core_temperature: Optional[CoreTemperatureSummary] = None
1092
1178
  smo2: Optional[Smo2Summary] = None
@@ -1133,6 +1219,13 @@ class RangeValueOutput(BaseModel):
1133
1219
  max: Optional[Value] = None
1134
1220
 
1135
1221
 
1222
+ class WebhookEventBody(BaseModel):
1223
+ user_id: str = Field(..., title='User Id')
1224
+ event_type: WebhookEventType
1225
+ resource_id: Optional[str] = Field(..., title='Resource Id')
1226
+ timestamp: datetime = Field(..., title='Timestamp')
1227
+
1228
+
1136
1229
  class IntervalInput(BaseModel):
1137
1230
  type: Literal['interval'] = Field('interval', title='Type')
1138
1231
  volume: Union[ConstantValueInput, RangeValueInput, RampValueInput] = Field(
@@ -1258,6 +1351,7 @@ class ActivityDetails(BaseModel):
1258
1351
  laps: Optional[List[Lap]] = Field(None, title='Laps')
1259
1352
  traces: Optional[List[TraceDetails]] = Field(None, title='Traces')
1260
1353
  distance: Optional[float] = Field(None, title='Distance')
1354
+ devices: Optional[List[str]] = Field(None, title='Devices')
1261
1355
  duration: timedelta = Field(..., title='Duration')
1262
1356
  start_local: datetime = Field(..., title='Start Local')
1263
1357
  end_local: datetime = Field(..., title='End Local')
@@ -122,6 +122,22 @@ Sport.is_sub_sport_of.__doc__ = _is_sub_sport_of.__doc__
122
122
  Sport.is_root_sport = _is_root_sport
123
123
  Sport.is_root_sport.__doc__ = _is_root_sport.__doc__
124
124
 
125
+ @classmethod
126
+ def _sport_missing(cls, value: str):
127
+ """Handle unknown sport values from newer API versions.
128
+
129
+ This allows the client to gracefully handle new sports added to the API
130
+ without requiring a client library update. Unknown values become dynamic
131
+ enum members that behave like regular Sport values.
132
+ """
133
+ pseudo_member = object.__new__(cls)
134
+ pseudo_member._name_ = value
135
+ pseudo_member._value_ = value
136
+ cls._value2member_map_[value] = pseudo_member # Cache for future lookups
137
+ return pseudo_member
138
+
139
+
140
+ Sport._missing_ = _sport_missing
125
141
  Sport.display_name = _display_name
126
142
  Sport.display_name.__doc__ = _display_name.__doc__
127
143
 
@@ -134,5 +150,34 @@ def _metric_display_name(metric: Metric) -> str:
134
150
  return metric.value.replace("_", " ")
135
151
 
136
152
 
153
+ @classmethod
154
+ def _metric_missing(cls, value: str):
155
+ """Handle unknown metric values from newer API versions.
156
+
157
+ This allows the client to gracefully handle new metrics added to the API
158
+ without requiring a client library update. Unknown values become dynamic
159
+ enum members that behave like regular Metric values.
160
+ """
161
+ pseudo_member = object.__new__(cls)
162
+ pseudo_member._name_ = value
163
+ pseudo_member._value_ = value
164
+ cls._value2member_map_[value] = pseudo_member # Cache for future lookups
165
+ return pseudo_member
166
+
167
+
168
+ Metric._missing_ = _metric_missing
137
169
  Metric.display_name = _metric_display_name
138
- Metric.display_name.__doc__ = _metric_display_name.__doc__
170
+ Metric.display_name.__doc__ = _metric_display_name.__doc__
171
+
172
+
173
+ @classmethod
174
+ def _scope_missing(cls, value: str):
175
+ """Handle unknown scope values from newer API versions."""
176
+ pseudo_member = object.__new__(cls)
177
+ pseudo_member._name_ = value
178
+ pseudo_member._value_ = value
179
+ cls._value2member_map_[value] = pseudo_member
180
+ return pseudo_member
181
+
182
+
183
+ Scope._missing_ = _scope_missing
@@ -2045,7 +2045,7 @@ wheels = [
2045
2045
 
2046
2046
  [[package]]
2047
2047
  name = "sweatstack"
2048
- version = "0.54.0"
2048
+ version = "0.58.0"
2049
2049
  source = { editable = "." }
2050
2050
  dependencies = [
2051
2051
  { name = "email-validator" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes