runtilities 2.3.0.dev2__py3-none-any.whl → 3.0.0.dev2__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.
running/runsignup.py CHANGED
@@ -1,25 +1,3 @@
1
- ###########################################################################################
2
- # runsignup - access methods for runsignup.com
3
- #
4
- # Date Author Reason
5
- # ---- ------ ------
6
- # 12/31/17 Lou King Create from runningahead.com
7
- #
8
- # Copyright 2017 Lou King
9
- #
10
- # Licensed under the Apache License, Version 2.0 (the "License");
11
- # you may not use this file except in compliance with the License.
12
- # You may obtain a copy of the License at
13
- #
14
- # http://www.apache.org/licenses/LICENSE-2.0
15
- #
16
- # Unless required by applicable law or agreed to in writing, software
17
- # distributed under the License is distributed on an "AS IS" BASIS,
18
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
- # See the License for the specific language governing permissions and
20
- # limitations under the License.
21
- #
22
- ###########################################################################################
23
1
  '''
24
2
  runsignup - access methods for runsignup.com
25
3
  ===================================================
@@ -37,7 +15,6 @@ from tempfile import NamedTemporaryFile
37
15
  # pypi
38
16
  from flask import current_app
39
17
  import requests
40
- from universalclient import Client as UniversalClient
41
18
 
42
19
  # github
43
20
 
@@ -52,10 +29,6 @@ names = NameDenormalizer()
52
29
 
53
30
  # use api.runsignup.com per https://info.runsignup.com/2025/08/06/upgrading-our-api-infrastructure-for-ai-api-runsignup-com/
54
31
 
55
- # login API (deprecated)
56
- login_url = 'https://api.runsignup.com/rest/login'
57
- logout_url = 'https://api.runsignup.com/rest/logout'
58
-
59
32
  # methods
60
33
  members_url = 'https://api.runsignup.com/rest/club/{club_id}/members'
61
34
  getrace_url = 'https://api.runsignup.com/rest/race/{race_id}'
@@ -74,21 +47,25 @@ class parameterError(Exception): pass
74
47
  thislogger = logging.getLogger("running.runsignup")
75
48
 
76
49
  ########################################################################
77
- class RunSignUp():
50
+ class RunSignupBase():
78
51
  '''
79
- access methods for RunSignUp.com
52
+ common session / authentication / low-level request handling for RunSignUp.com REST API clients
80
53
 
81
- either key and secret OR email and password should be supplied
82
- key and secret take precedence
54
+ key and secret should be supplied for authenticated access
55
+
56
+ per https://info.runsignup.com/2026/07/17/new-api-registration-requirements/, all API callers must
57
+ register (free) and supply the resulting token/secret on every call starting 2027-01-01; existing
58
+ api_key/api_secret credentials are unaffected and continue to be required alongside these
83
59
 
84
60
  :param key: key from runsignup (direct key, no OAuth)
85
61
  :param secret: secret from runsignup (direct secret, no OAuth)
86
- :param email: email for use by Login API (deprecated)
87
- :param password: password for use by Login API (deprecated)
62
+ :param api_reg_token: API caller registration token, sent as rsu_api_reg GET parameter
63
+ :param api_reg_secret: API caller registration secret, sent as X-RSU-API-REG-SECRET header
88
64
  :param debug: set to True for debug logging of http requests, default False
89
65
  '''
90
66
 
91
- def __init__(self, userpriv=False, key=None, secret=None, email=None, password=None, debug=False):
67
+ def __init__(self, userpriv=False, key=None, secret=None,
68
+ api_reg_token=None, api_reg_secret=None, debug=False):
92
69
  """
93
70
  initialize
94
71
  """
@@ -108,27 +85,20 @@ class RunSignUp():
108
85
  requests_log.setLevel(logging.NOTSET)
109
86
  requests_log.propagate = False
110
87
 
111
- self.userpriv = False
112
- if (not key and not email):
113
- self.userpriv = True
88
+ self.userpriv = not key
114
89
  if (key and not secret) or (secret and not key):
115
90
  raise parameterError('key and secret must be supplied together')
116
- if (email and not password) or (password and not email):
117
- raise parameterError('email and password must be supplied together')
91
+ if (api_reg_token and not api_reg_secret) or (api_reg_secret and not api_reg_token):
92
+ raise parameterError('api_reg_token and api_reg_secret must be supplied together')
118
93
 
119
94
  self.key = key
120
95
  self.secret = secret
121
- self.email = email
122
- self.password = password
96
+ self.api_reg_token = api_reg_token
97
+ self.api_reg_secret = api_reg_secret
123
98
  self.debug = debug
124
99
  self.client_credentials = {}
125
100
 
126
- if self.userpriv:
127
- self.credentials_type = 'none'
128
- elif key:
129
- self.credentials_type = 'key'
130
- else:
131
- self.credentials_type = 'login'
101
+ self.credentials_type = 'none' if self.userpriv else 'key'
132
102
 
133
103
  def __enter__(self):
134
104
  self.open()
@@ -142,22 +112,18 @@ class RunSignUp():
142
112
  # set up session for multiple requests
143
113
  self.session = requests.Session()
144
114
 
115
+ # per https://info.runsignup.com/2026/07/17/new-api-registration-requirements/, registration
116
+ # secret is sent as a header, applies regardless of credentials_type
117
+ if self.api_reg_secret:
118
+ self.session.headers.update({'X-RSU-API-REG-SECRET': self.api_reg_secret})
119
+
145
120
  if not self.userpriv:
146
- # key / secret supplied - this take precedence
147
- if self.credentials_type == 'key':
148
- self.client_credentials = {'api_key' : self.key,
149
- 'api_secret' : self.secret}
150
-
151
- # email / password supplied
152
- else:
153
- # login to runsignup - note temporary keys will expire 1 hour after last API call
154
- # see https://api.runsignup.com/API/login/POST
155
- r = requests.post(login_url, params={'format' : 'json'}, data={'email' : self.email, 'password' : self.password})
156
- resp = r.json()
157
-
158
- self.credentials_type = 'login'
159
- self.client_credentials = {'tmp_key' : resp['tmp_key'],
160
- 'tmp_secret' : resp['tmp_secret']}
121
+ self.client_credentials = {'api_key' : self.key,
122
+ 'api_secret' : self.secret}
123
+
124
+ # registration token is sent as a GET parameter alongside whichever credentials were set above
125
+ if self.api_reg_token:
126
+ self.client_credentials['rsu_api_reg'] = self.api_reg_token
161
127
 
162
128
  def close(self):
163
129
  '''
@@ -166,7 +132,61 @@ class RunSignUp():
166
132
  self.client_credentials = {}
167
133
  self.session.close()
168
134
 
169
- # TODO: should we also log out?
135
+ def _rsugetcsv(self, methodurl, **payload):
136
+ """
137
+ get method for runsignup access (csv format response)
138
+
139
+ :param methodurl: runsignup method url to call
140
+ :param contentfield: content field to retrieve from response
141
+ :param **payload: parameters for the method
142
+ """
143
+
144
+ thispayload = self.client_credentials.copy()
145
+ thispayload.update(payload)
146
+ thispayload.update({'format':'csv'})
147
+
148
+ resp = self.session.get(methodurl, params=thispayload)
149
+ if resp.status_code != 200:
150
+ raise accessError('HTTP response code={}, url={}'.format(resp.status_code,resp.url))
151
+
152
+ data = resp.text
153
+
154
+ # if 'error' in data:
155
+ # raise accessError('RSU response code={}-{}, url={}'.format(data['error']['error_code'],data['error']['error_msg'],resp.url))
156
+
157
+ return data
158
+
159
+ def _rsuget(self, methodurl, **payload):
160
+ """
161
+ get method for runsignup access (json format response)
162
+
163
+ :param methodurl: runsignup method url to call
164
+ :param contentfield: content field to retrieve from response
165
+ :param **payload: parameters for the method
166
+ """
167
+
168
+ thispayload = self.client_credentials.copy()
169
+ thispayload.update(payload)
170
+ thispayload.update({'format':'json'})
171
+
172
+ resp = self.session.get(methodurl, params=thispayload)
173
+ if resp.status_code != 200:
174
+ raise accessError('HTTP response code={}, url={}'.format(resp.status_code,resp.url))
175
+
176
+ data = resp.json()
177
+
178
+ if 'error' in data:
179
+ raise accessError('RSU response code={}-{}, url={}'.format(data['error']['error_code'],data['error']['error_msg'],resp.url))
180
+
181
+ return data
182
+
183
+ ########################################################################
184
+ class RunSignUp(RunSignupBase):
185
+ '''
186
+ access methods for RunSignUp.com
187
+
188
+ see :class:`RunSignupBase` for authentication / session parameters
189
+ '''
170
190
 
171
191
  def members(self, club_id, **kwargs):
172
192
  '''
@@ -371,56 +391,9 @@ class RunSignUp():
371
391
 
372
392
  return '\n'.join([header] + results)
373
393
 
374
- def _rsugetcsv(self, methodurl, **payload):
375
- """
376
- get method for runsignup access (csv format response)
377
-
378
- :param methodurl: runsignup method url to call
379
- :param contentfield: content field to retrieve from response
380
- :param **payload: parameters for the method
381
- """
382
-
383
- thispayload = self.client_credentials.copy()
384
- thispayload.update(payload)
385
- thispayload.update({'format':'csv'})
386
-
387
- resp = self.session.get(methodurl, params=thispayload)
388
- if resp.status_code != 200:
389
- raise accessError('HTTP response code={}, url={}'.format(resp.status_code,resp.url))
390
-
391
- data = resp.text
392
-
393
- # if 'error' in data:
394
- # raise accessError('RSU response code={}-{}, url={}'.format(data['error']['error_code'],data['error']['error_msg'],resp.url))
395
-
396
- return data
397
-
398
- def _rsuget(self, methodurl, **payload):
399
- """
400
- get method for runsignup access (json format response)
401
-
402
- :param methodurl: runsignup method url to call
403
- :param contentfield: content field to retrieve from response
404
- :param **payload: parameters for the method
405
- """
406
-
407
- thispayload = self.client_credentials.copy()
408
- thispayload.update(payload)
409
- thispayload.update({'format':'json'})
410
-
411
- resp = self.session.get(methodurl, params=thispayload)
412
- if resp.status_code != 200:
413
- raise accessError('HTTP response code={}, url={}'.format(resp.status_code,resp.url))
414
-
415
- data = resp.json()
416
-
417
- if 'error' in data:
418
- raise accessError('RSU response code={}-{}, url={}'.format(data['error']['error_code'],data['error']['error_msg'],resp.url))
419
-
420
- return data
421
-
422
394
  #----------------------------------------------------------------------
423
- def updatemembercache(club_id, membercachefilename, key=None, secret=None, email=None, password=None, debug=False):
395
+ def updatemembercache(club_id, membercachefilename, key=None, secret=None,
396
+ api_reg_token=None, api_reg_secret=None, debug=False):
424
397
  if debug:
425
398
  # set up debug logging
426
399
  thislogger.setLevel(logging.DEBUG)
@@ -431,7 +404,8 @@ def updatemembercache(club_id, membercachefilename, key=None, secret=None, email
431
404
  thislogger.propagate = True
432
405
 
433
406
  # set up access to RunSignUp
434
- rsu = RunSignUp(key=key, secret=secret, email=email, password=password, debug=debug)
407
+ rsu = RunSignUp(key=key, secret=secret,
408
+ api_reg_token=api_reg_token, api_reg_secret=api_reg_secret, debug=debug)
435
409
  rsu.open()
436
410
 
437
411
  # transform from RunSignUp to membercache format
@@ -608,7 +582,8 @@ def updatemembercache(club_id, membercachefilename, key=None, secret=None, email
608
582
  return rsumembers
609
583
 
610
584
  #----------------------------------------------------------------------
611
- def members2csv(club_id, key, secret, mapping, filepath=None, encoding=None):
585
+ def members2csv(club_id, key, secret, mapping, filepath=None, encoding=None,
586
+ api_reg_token=None, api_reg_secret=None):
612
587
  '''
613
588
  Access club_id through RunSignUp API to retrieve members. Return
614
589
  list of members as if csv file. Optionally save csv file.
@@ -619,11 +594,13 @@ def members2csv(club_id, key, secret, mapping, filepath=None, encoding=None):
619
594
  :param mapping: OrderedDict {'outfield1':'infield1', 'outfield2':outfunction(inrec), ...} or ['inoutfield1', 'inoutfield2', ...]
620
595
  :param normfile: (optional) pathname to save csv file
621
596
  :param encoding: (optional) encoding to use for file
597
+ :param api_reg_token: API caller registration token, sent as rsu_api_reg GET parameter
598
+ :param api_reg_secret: API caller registration secret, sent as X-RSU-API-REG-SECRET header
622
599
  :rtype: csv file records, as list
623
600
  '''
624
601
 
625
602
  # get the members from the RunSignUp API
626
- with RunSignUp(key=key, secret=secret) as rsu:
603
+ with RunSignUp(key=key, secret=secret, api_reg_token=api_reg_token, api_reg_secret=api_reg_secret) as rsu:
627
604
  members = rsu.members(club_id)
628
605
 
629
606
  filerows = record2csv(members, mapping, filepath, encoding=encoding)
@@ -873,34 +850,3 @@ class ClubMemberships():
873
850
  for member in self.userid2mem
874
851
  )
875
852
 
876
- class RunSignupFluent(UniversalClient):
877
- from rauth import OAuth2Service
878
-
879
- '''
880
- Fluent interface to RunSignUp API -- see https://universal-client.readthedocs.io
881
- '''
882
- def __init__(self, key=None, secret=None, debug=False):
883
- '''
884
- initialize RunSignUp Fluent client
885
-
886
- :param key: api key for RunSignUp
887
- :param secret: api secret for RunSignUp
888
- :param debug: debug flag
889
- '''
890
- # rsuservice = Oauth2Service(
891
- # name='runsignup',
892
- # authorize_url='https://www.runsignup.com/oauth2/authorize',
893
- # access_token_url='https://api.runsignup.com/oauth2/token',
894
- # client_id=key,
895
- # client_secret=secret,
896
- # )
897
-
898
- self._params = params = {'api_key' : key,
899
- 'api_secret' : secret,
900
- 'format' : 'json' }
901
-
902
- super().__init__(
903
- url='https://api.runsignup.com/rest',
904
- params=params,
905
- )
906
-
@@ -0,0 +1,36 @@
1
+ '''
2
+ runsignup_fluent - fluent access to runsignup.com
3
+ ===================================================
4
+ '''
5
+
6
+ # pypi
7
+ from universalclient import Client as UniversalClient
8
+
9
+ class RunSignupFluent(UniversalClient):
10
+
11
+ '''
12
+ Fluent interface to RunSignUp API -- see https://universal-client.readthedocs.io
13
+ '''
14
+ def __init__(self, key=None, secret=None, api_reg_token=None, api_reg_secret=None, debug=False):
15
+ '''
16
+ initialize RunSignUp Fluent client
17
+
18
+ :param key: api key for RunSignUp
19
+ :param secret: api secret for RunSignUp
20
+ :param api_reg_token: API caller registration token, sent as rsu_api_reg GET parameter -- see
21
+ https://info.runsignup.com/2026/07/17/new-api-registration-requirements/
22
+ :param api_reg_secret: API caller registration secret, sent as X-RSU-API-REG-SECRET header
23
+ :param debug: debug flag
24
+ '''
25
+
26
+ self._params = params = {'api_key' : key,
27
+ 'api_secret' : secret,
28
+ 'format' : 'json' }
29
+ if api_reg_token:
30
+ params['rsu_api_reg'] = api_reg_token
31
+
32
+ client_kwargs = {'url': 'https://api.runsignup.com/rest', 'params': params}
33
+ if api_reg_secret:
34
+ client_kwargs['headers'] = {'X-RSU-API-REG-SECRET': api_reg_secret}
35
+
36
+ super().__init__(**client_kwargs)
running/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  # this string is used for the version string in the documentation, as well as the egg
2
- __version__ = '2.3.0.dev2'
2
+ __version__ = '3.0.0.dev2'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runtilities
3
- Version: 2.3.0.dev2
3
+ Version: 3.0.0.dev2
4
4
  Summary: running related scripts
5
5
  Home-page: http://github.com/louking/running
6
6
  Author: Lou King
@@ -19,27 +19,28 @@ running/runningahead.py,sha256=DbIwMGC_gydXO-NPjL08fTqc1TBDLPgvQLMlvji6MOM,18203
19
19
  running/runningaheadmembers.py,sha256=7jaPpWha4AyG6eCbQAJyK1d7zX_doMx-zT_r7reIDoM,14089
20
20
  running/runningaheadparticipants.py,sha256=5Udfjuj5MaXWRhk_5MsdJsvsPW5r64PwCMPNP6IJfhE,6167
21
21
  running/runningaheadresults.py,sha256=6rBIcq3TGIPlA36x8iCdgEaFHSYSMxO6HpRruYG1R8I,15840
22
- running/runsignup.py,sha256=UxxM2GemWcYbzg2JRQem2BQFDJfE8240ip1eSPUFnAM,37856
22
+ running/runsignup.py,sha256=G1tTxxwuzahWw0dMlkTJWAUGeFmRXQlmRiE2Z3ENzOA,36280
23
+ running/runsignup_fluent.py,sha256=gbmGz6HpS5wSTroFsrsJfqxCU1dndMGwDx9r9z8aGk8,1371
23
24
  running/strava.py,sha256=qRNsCdtlnh11nnSv7jaz9cljRtji75MRmJri_3M3CCA,16841
24
25
  running/textnormalize.py,sha256=2SVOcbQNW-VpDwsdmB2AFSYSdmXgIBA8Z8LBKmqQSEc,2294
25
26
  running/ultrasignup.py,sha256=VDSM9I84g2H7Xsol-56KV3m-Uxogz-1OryOK1jEYWDQ,14863
26
27
  running/ultrasignupresults.py,sha256=ruc211mIBgSg5curRX9u298rZxpiWN127DeZ_7lE2Yg,15182
27
- running/version.py,sha256=-gpiglTkf_D-h5iBywpsUzVwVN7WfcYhtg8RwdLc3fQ,115
28
+ running/version.py,sha256=8An5zn47H3upQA8Ouvox6d0vpV3s-MGyZ45env5nWYc,115
28
29
  running/xmldict.py,sha256=O7kvH313htiYeKRDm8jlOnDRmvErcYyhT9Hu2fTL7xI,3199
29
30
  running/xmldict1.py,sha256=-WERckaQlskGHdU73unmu7iZbIvXjOtjCUrE-YVFjSY,2703
30
31
  running/xmldict2.py,sha256=GxKj7E35_WagADj_6HJ8DeFbM31UMkVkn85-jdFoi08,2650
31
32
  running/xmlreader.py,sha256=V-Y-otEPyKv6muv4Mqm8eO5sc3uhA04eaaiTemSYqcU,2421
32
- runtilities-2.3.0.dev2.data/scripts/analyzeagegrade.py,sha256=M3k-Z1HRESjbANdk1fWtZ8714bdRIkwWOCG70Aq-pik,31505
33
- runtilities-2.3.0.dev2.data/scripts/athlinksresults.py,sha256=0WjEV2kh3hS3BOSMdd0XlDJNBv-5knQ7dOpIKIbOVYM,15852
34
- runtilities-2.3.0.dev2.data/scripts/competitor2csv.py,sha256=29Gq8Isx3VLgvADSImNUJjxGJyPyqWbjKHMYZ2xGzxk,5635
35
- runtilities-2.3.0.dev2.data/scripts/parseralogxml.py,sha256=al3yh0g-9wBWRQbjR4h6kooUjoe7bNlgV7lJDzxKo34,2520
36
- runtilities-2.3.0.dev2.data/scripts/parseresults.py,sha256=jbMcenx_9X6fzZX5i9wIgQfkCv-RmkK_F4xeFSr-vAo,3371
37
- runtilities-2.3.0.dev2.data/scripts/parsetcx.py,sha256=vxyNIkVjvHn_CSE2zrwRja4s5w55q3xBvsIhijarGis,8855
38
- runtilities-2.3.0.dev2.data/scripts/renderclubagstats.py,sha256=mAU9KSCBQIEbACT4X7nTHzcfYJooK5dAtazuAhophYM,20984
39
- runtilities-2.3.0.dev2.data/scripts/runningaheadresults.py,sha256=RVWO5eUQlOegeK-gV-2tUGR1eFbRnPP1p_Gxrm2vNtQ,15831
40
- runtilities-2.3.0.dev2.data/scripts/ultrasignupresults.py,sha256=ouP4RHHmJbaBODacVf7dSxDyIQwRymXoEJjX9P2GTpg,15173
41
- runtilities-2.3.0.dev2.dist-info/METADATA,sha256=3aGFXRXsuZcO9pnXyqS85sle7fTk1V4F8gzulbmmchE,1343
42
- runtilities-2.3.0.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
43
- runtilities-2.3.0.dev2.dist-info/entry_points.txt,sha256=wAk1YhCvwv-J7D113sQLjKzSfKxm6NS1gb_NCl1eowQ,433
44
- runtilities-2.3.0.dev2.dist-info/top_level.txt,sha256=pOzFyH2BG7_3oRjBfvjPgzGMtiOhA8FbhUkImqKn8Kw,8
45
- runtilities-2.3.0.dev2.dist-info/RECORD,,
33
+ runtilities-3.0.0.dev2.data/scripts/analyzeagegrade.py,sha256=M3k-Z1HRESjbANdk1fWtZ8714bdRIkwWOCG70Aq-pik,31505
34
+ runtilities-3.0.0.dev2.data/scripts/athlinksresults.py,sha256=0WjEV2kh3hS3BOSMdd0XlDJNBv-5knQ7dOpIKIbOVYM,15852
35
+ runtilities-3.0.0.dev2.data/scripts/competitor2csv.py,sha256=29Gq8Isx3VLgvADSImNUJjxGJyPyqWbjKHMYZ2xGzxk,5635
36
+ runtilities-3.0.0.dev2.data/scripts/parseralogxml.py,sha256=al3yh0g-9wBWRQbjR4h6kooUjoe7bNlgV7lJDzxKo34,2520
37
+ runtilities-3.0.0.dev2.data/scripts/parseresults.py,sha256=jbMcenx_9X6fzZX5i9wIgQfkCv-RmkK_F4xeFSr-vAo,3371
38
+ runtilities-3.0.0.dev2.data/scripts/parsetcx.py,sha256=vxyNIkVjvHn_CSE2zrwRja4s5w55q3xBvsIhijarGis,8855
39
+ runtilities-3.0.0.dev2.data/scripts/renderclubagstats.py,sha256=mAU9KSCBQIEbACT4X7nTHzcfYJooK5dAtazuAhophYM,20984
40
+ runtilities-3.0.0.dev2.data/scripts/runningaheadresults.py,sha256=RVWO5eUQlOegeK-gV-2tUGR1eFbRnPP1p_Gxrm2vNtQ,15831
41
+ runtilities-3.0.0.dev2.data/scripts/ultrasignupresults.py,sha256=ouP4RHHmJbaBODacVf7dSxDyIQwRymXoEJjX9P2GTpg,15173
42
+ runtilities-3.0.0.dev2.dist-info/METADATA,sha256=EijARS-NTmMrvaZYGrVyKiHgiiyrLm9ReFpoKeVRW0Q,1343
43
+ runtilities-3.0.0.dev2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
44
+ runtilities-3.0.0.dev2.dist-info/entry_points.txt,sha256=wAk1YhCvwv-J7D113sQLjKzSfKxm6NS1gb_NCl1eowQ,433
45
+ runtilities-3.0.0.dev2.dist-info/top_level.txt,sha256=pOzFyH2BG7_3oRjBfvjPgzGMtiOhA8FbhUkImqKn8Kw,8
46
+ runtilities-3.0.0.dev2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (83.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5