kaggle 1.7.4.5__py3-none-any.whl → 1.8.2__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 (110) hide show
  1. kaggle/__init__.py +10 -6
  2. kaggle/api/kaggle_api.py +574 -598
  3. kaggle/api/kaggle_api_extended.py +5251 -4769
  4. kaggle/cli.py +1335 -1585
  5. kaggle/models/api_blob_type.py +3 -3
  6. kaggle/models/dataset_column.py +165 -174
  7. kaggle/models/dataset_new_request.py +83 -41
  8. kaggle/models/dataset_new_version_request.py +32 -25
  9. kaggle/models/dataset_update_settings_request.py +35 -27
  10. kaggle/models/kaggle_models_extended.py +169 -172
  11. kaggle/models/kernel_push_request.py +66 -49
  12. kaggle/models/model_instance_new_version_request.py +10 -18
  13. kaggle/models/model_instance_update_request.py +103 -34
  14. kaggle/models/model_new_instance_request.py +138 -41
  15. kaggle/models/model_new_request.py +35 -27
  16. kaggle/models/model_update_request.py +32 -25
  17. kaggle/models/start_blob_upload_request.py +192 -195
  18. kaggle/models/start_blob_upload_response.py +98 -98
  19. kaggle/models/upload_file.py +114 -120
  20. kaggle/test/test_authenticate.py +23 -23
  21. {kaggle-1.7.4.5.dist-info → kaggle-1.8.2.dist-info}/METADATA +12 -15
  22. kaggle-1.8.2.dist-info/RECORD +148 -0
  23. kagglesdk/__init__.py +5 -1
  24. kagglesdk/benchmarks/services/__init__.py +0 -0
  25. kagglesdk/benchmarks/services/benchmarks_api_service.py +19 -0
  26. kagglesdk/benchmarks/types/__init__.py +0 -0
  27. kagglesdk/benchmarks/types/benchmark_types.py +307 -0
  28. kagglesdk/benchmarks/types/benchmarks_api_service.py +243 -0
  29. kagglesdk/blobs/services/blob_api_service.py +1 -1
  30. kagglesdk/blobs/types/blob_api_service.py +2 -2
  31. kagglesdk/common/services/__init__.py +0 -0
  32. kagglesdk/common/services/operations_service.py +46 -0
  33. kagglesdk/common/types/file_download.py +1 -1
  34. kagglesdk/common/types/http_redirect.py +1 -1
  35. kagglesdk/common/types/operations.py +194 -0
  36. kagglesdk/common/types/operations_service.py +48 -0
  37. kagglesdk/community/__init__.py +0 -0
  38. kagglesdk/community/types/__init__.py +0 -0
  39. kagglesdk/community/types/content_enums.py +44 -0
  40. kagglesdk/community/types/organization.py +410 -0
  41. kagglesdk/competitions/services/competition_api_service.py +49 -12
  42. kagglesdk/competitions/types/competition.py +14 -0
  43. kagglesdk/competitions/types/competition_api_service.py +1639 -1275
  44. kagglesdk/competitions/types/search_competitions.py +28 -0
  45. kagglesdk/datasets/databundles/__init__.py +0 -0
  46. kagglesdk/datasets/databundles/types/__init__.py +0 -0
  47. kagglesdk/datasets/databundles/types/databundle_api_types.py +540 -0
  48. kagglesdk/datasets/services/dataset_api_service.py +39 -14
  49. kagglesdk/datasets/types/dataset_api_service.py +554 -300
  50. kagglesdk/datasets/types/dataset_enums.py +21 -0
  51. kagglesdk/datasets/types/dataset_service.py +145 -0
  52. kagglesdk/datasets/types/dataset_types.py +74 -74
  53. kagglesdk/datasets/types/search_datasets.py +6 -0
  54. kagglesdk/discussions/__init__.py +0 -0
  55. kagglesdk/discussions/types/__init__.py +0 -0
  56. kagglesdk/discussions/types/search_discussions.py +43 -0
  57. kagglesdk/discussions/types/writeup_enums.py +11 -0
  58. kagglesdk/education/services/education_api_service.py +1 -1
  59. kagglesdk/education/types/education_api_service.py +1 -1
  60. kagglesdk/kaggle_client.py +46 -23
  61. kagglesdk/kaggle_creds.py +148 -0
  62. kagglesdk/kaggle_env.py +89 -25
  63. kagglesdk/kaggle_http_client.py +224 -306
  64. kagglesdk/kaggle_oauth.py +200 -0
  65. kagglesdk/kaggle_object.py +286 -293
  66. kagglesdk/kernels/services/kernels_api_service.py +46 -9
  67. kagglesdk/kernels/types/kernels_api_service.py +635 -159
  68. kagglesdk/kernels/types/kernels_enums.py +6 -0
  69. kagglesdk/kernels/types/search_kernels.py +6 -0
  70. kagglesdk/licenses/__init__.py +0 -0
  71. kagglesdk/licenses/types/__init__.py +0 -0
  72. kagglesdk/licenses/types/licenses_types.py +182 -0
  73. kagglesdk/models/services/model_api_service.py +41 -17
  74. kagglesdk/models/types/model_api_service.py +987 -637
  75. kagglesdk/models/types/model_enums.py +8 -0
  76. kagglesdk/models/types/model_service.py +71 -71
  77. kagglesdk/models/types/model_types.py +1057 -5
  78. kagglesdk/models/types/search_models.py +8 -0
  79. kagglesdk/search/__init__.py +0 -0
  80. kagglesdk/search/services/__init__.py +0 -0
  81. kagglesdk/search/services/search_api_service.py +19 -0
  82. kagglesdk/search/types/__init__.py +0 -0
  83. kagglesdk/search/types/search_api_service.py +2435 -0
  84. kagglesdk/search/types/search_content_shared.py +50 -0
  85. kagglesdk/search/types/search_enums.py +45 -0
  86. kagglesdk/search/types/search_service.py +303 -0
  87. kagglesdk/security/services/iam_service.py +31 -0
  88. kagglesdk/security/services/oauth_service.py +27 -1
  89. kagglesdk/security/types/authentication.py +63 -63
  90. kagglesdk/security/types/iam_service.py +496 -0
  91. kagglesdk/security/types/oauth_service.py +797 -10
  92. kagglesdk/security/types/roles.py +8 -0
  93. kagglesdk/security/types/security_types.py +159 -0
  94. kagglesdk/test/__init__.py +0 -0
  95. kagglesdk/test/test_client.py +20 -22
  96. kagglesdk/users/services/account_service.py +13 -1
  97. kagglesdk/users/services/group_api_service.py +31 -0
  98. kagglesdk/users/types/account_service.py +169 -28
  99. kagglesdk/users/types/group_api_service.py +315 -0
  100. kagglesdk/users/types/group_types.py +165 -0
  101. kagglesdk/users/types/groups_enum.py +8 -0
  102. kagglesdk/users/types/progression_service.py +9 -0
  103. kagglesdk/users/types/search_users.py +23 -0
  104. kagglesdk/users/types/user_avatar.py +226 -0
  105. kaggle/configuration.py +0 -206
  106. kaggle-1.7.4.5.dist-info/RECORD +0 -98
  107. {kaggle-1.7.4.5.dist-info → kaggle-1.8.2.dist-info}/WHEEL +0 -0
  108. {kaggle-1.7.4.5.dist-info → kaggle-1.8.2.dist-info}/entry_points.txt +0 -0
  109. {kaggle-1.7.4.5.dist-info → kaggle-1.8.2.dist-info}/licenses/LICENSE.txt +0 -0
  110. {kaggle/test → kagglesdk/benchmarks}/__init__.py +0 -0
@@ -4,12 +4,20 @@ import json
4
4
  import os
5
5
  import urllib.parse
6
6
  from io import BytesIO
7
+ from pathlib import Path
7
8
 
8
9
  import requests
9
10
  from urllib3.fields import RequestField
10
11
 
11
- from kagglesdk.kaggle_env import get_endpoint, get_env, KaggleEnv
12
+ from kagglesdk.kaggle_env import (
13
+ get_endpoint,
14
+ get_env,
15
+ get_access_token_from_env,
16
+ KaggleEnv,
17
+ )
12
18
  from kagglesdk.kaggle_object import KaggleObject
19
+ from kagglesdk.common.types.file_download import FileDownload
20
+ from kagglesdk.common.types.http_redirect import HttpRedirect
13
21
  from typing import Type
14
22
 
15
23
  # TODO (http://b/354237483) Generate the client from the existing one.
@@ -17,335 +25,245 @@ from typing import Type
17
25
  # auth handling. The new client requires KAGGLE_API_TOKEN, so it is not
18
26
  # currently usable by the CLI.
19
27
 
20
- # TODO: Extend kapigen to add a boolean to these requests indicating that they use forms.
21
- REQUESTS_REQUIRING_FORMS = [
22
- 'ApiUploadDatasetFileRequest',
23
- 'ApiCreateSubmissionRequest',
24
- 'ApiCreateCodeSubmissionRequest',
25
- 'ApiStartSubmissionUploadRequest',
26
- 'ApiUploadModelFileRequest',
27
- ]
28
-
29
28
 
30
29
  def _headers_to_str(headers):
31
- return '\n'.join(f'{k}: {v}' for k, v in headers.items())
30
+ return "\n".join(f"{k}: {v}" for k, v in headers.items())
32
31
 
33
32
 
34
33
  def _get_apikey_creds():
35
- apikey_filename = os.path.expanduser('~/.kaggle/kaggle.json')
36
- if not os.path.exists(apikey_filename):
37
- return None
38
-
39
- kaggle_json = None
40
- with open(apikey_filename) as apikey_file:
41
- kaggle_json = apikey_file.read()
42
-
43
- if not kaggle_json or not kaggle_json.strip():
44
- return None
45
-
46
- api_key_data = json.loads(kaggle_json)
47
- username = api_key_data['username']
48
- api_key = api_key_data['key']
49
- return username, api_key
50
-
51
-
52
- def clean_data(data):
53
- if isinstance(data, dict):
54
- return {
55
- to_lower_camel_case(k): clean_data(v) for k, v in data.items() if v is not None
56
- }
57
- if isinstance(data, list):
58
- return [clean_data(v) for v in data if v is not None]
59
- if data is True:
60
- return 'true'
61
- if data is False:
62
- return 'false'
63
- return data
64
-
34
+ apikey_filename = os.path.expanduser("~/.kaggle/kaggle.json")
35
+ if not os.path.exists(apikey_filename):
36
+ return None
65
37
 
66
- def find_words(source, left='{', right='}'):
67
- words = []
68
- split_str = source.split(left)
38
+ kaggle_json = None
39
+ with open(apikey_filename) as apikey_file:
40
+ kaggle_json = apikey_file.read()
69
41
 
70
- for s in split_str[1:]:
71
- split_s = s.split(right)
72
- if len(split_s) > 1:
73
- words.append(split_s[0])
42
+ if not kaggle_json or not kaggle_json.strip():
43
+ return None
74
44
 
75
- return words
45
+ api_key_data = json.loads(kaggle_json)
46
+ username = api_key_data["username"]
47
+ api_key = api_key_data["key"]
48
+ return username, api_key
76
49
 
77
50
 
78
- def to_camel_case(snake_str):
79
- return ''.join(x.capitalize() for x in snake_str.lower().split('_'))
80
-
51
+ class KaggleHttpClient(object):
52
+ _xsrf_cookie_name = "XSRF-TOKEN"
53
+ _csrf_cookie_name = "CSRF-TOKEN"
54
+ _xsrf_cookies = (_xsrf_cookie_name, _csrf_cookie_name)
55
+ _xsrf_header_name = "X-XSRF-TOKEN"
56
+
57
+ def __init__(
58
+ self,
59
+ env: KaggleEnv = None,
60
+ verbose: bool = False,
61
+ username: str = None,
62
+ password: str = None,
63
+ api_token: str = None,
64
+ ):
65
+ self._env = env or get_env()
66
+ self._signed_in = None
67
+ self._endpoint = get_endpoint(self._env)
68
+ self._verbose = verbose
69
+ self._session = None
70
+ self._username = username
71
+ self._password = password
72
+ self._api_token = api_token
73
+
74
+ def call(
75
+ self,
76
+ service_name: str,
77
+ request_name: str,
78
+ request: KaggleObject,
79
+ response_type: Type[KaggleObject],
80
+ ):
81
+ self._init_session()
82
+ http_request = self._prepare_request(service_name, request_name, request)
83
+
84
+ # Merge environment settings into session
85
+ settings = self._session.merge_environment_settings(http_request.url, {}, None, None, None)
86
+
87
+ # Use stream=True for file downloads to avoid loading entire file into memory
88
+ # See: https://github.com/Kaggle/kaggle-api/issues/754
89
+ if response_type is not None and (response_type == FileDownload or response_type == HttpRedirect):
90
+ settings["stream"] = True
91
+
92
+ http_response = self._session.send(http_request, **settings)
93
+
94
+ response = self._prepare_response(response_type, http_response)
95
+ return response
96
+
97
+ def _prepare_request(self, service_name: str, request_name: str, request: KaggleObject):
98
+ request_url = self._get_request_url(service_name, request_name)
99
+ http_request = requests.Request(
100
+ method="POST",
101
+ url=request_url,
102
+ json=request.__class__.to_dict(request),
103
+ headers=self._session.headers,
104
+ auth=self._session.auth,
105
+ )
106
+ prepared_request = http_request.prepare()
107
+ self._print_request(prepared_request)
108
+ return prepared_request
109
+
110
+ def _prepare_response(self, response_type, http_response):
111
+ """Extract the kaggle response and raise an exception if it is an error."""
112
+ self._print_response(http_response)
113
+ try:
114
+ if "application/json" in http_response.headers["Content-Type"]:
115
+ resp = http_response.json()
116
+ if "code" in resp and resp["code"] >= 400:
117
+ raise requests.exceptions.HTTPError(resp["message"], response=http_response)
118
+ except KeyError:
119
+ pass
120
+ http_response.raise_for_status()
121
+ if response_type is None: # Method doesn't have a return type
122
+ return None
123
+ return response_type.prepare_from(http_response)
124
+
125
+ def _print_request(self, request):
126
+ if not self._verbose:
127
+ return
128
+ self._print("---------------------Request----------------------")
129
+ self._print(f"{request.method} {request.url}\n{_headers_to_str(request.headers)}\n\n{request.body}")
130
+ self._print("--------------------------------------------------")
131
+
132
+ def _print_response(self, response, body=True):
133
+ if not self._verbose:
134
+ return
135
+ self._print("---------------------Response---------------------")
136
+ self._print(f"{response.status_code}\n{_headers_to_str(response.headers)}")
137
+ if body:
138
+ self._print(f"\n{response.text}")
139
+ self._print("--------------------------------------------------")
140
+
141
+ def _print(self, message: str):
142
+ if self._verbose:
143
+ print(message)
144
+
145
+ def __enter__(self):
146
+ self._init_session()
147
+ return self
148
+
149
+ def __exit__(self, exc_type, exc_value, tb):
150
+ if self._session is not None:
151
+ self._session.close()
152
+
153
+ def _init_session(self):
154
+ if self._session is not None:
155
+ return self._session
156
+
157
+ self._session = requests.Session()
158
+ self._session.headers.update({"User-Agent": "kaggle-api/v1.8.0", "Content-Type": "application/json"}) # Was: V2
159
+
160
+ iap_token = self._get_iap_token_if_required()
161
+ if iap_token is not None:
162
+ self._session.headers.update(
163
+ {
164
+ # https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_proxy-authorization_header
165
+ "Proxy-Authorization": f"Bearer {iap_token}",
166
+ }
167
+ )
168
+
169
+ self._try_fill_auth()
170
+ # self._fill_xsrf_token(iap_token) # TODO Make this align with original handler.
171
+
172
+ def _get_iap_token_if_required(self):
173
+ if self._env not in (KaggleEnv.STAGING, KaggleEnv.ADMIN):
174
+ return None
175
+ iap_token = os.getenv("KAGGLE_IAP_TOKEN")
176
+ if iap_token is None:
177
+ raise Exception(f'Must set KAGGLE_IAP_TOKEN to access "{self._endpoint}"')
178
+ return iap_token
179
+
180
+ def _fill_xsrf_token(self, iap_token):
181
+ initial_get_request = requests.Request(
182
+ method="GET",
183
+ url=self._endpoint,
184
+ headers=self._session.headers,
185
+ auth=self._session.auth,
186
+ )
187
+ prepared_request = initial_get_request.prepare()
188
+ self._print_request(prepared_request)
81
189
 
82
- def to_lower_camel_case(snake_str):
83
- # https://stackoverflow.com/questions/19053707/converting-snake-case-to-lower-camel-case-lowercamelcase
84
- # We capitalize the first letter of each component except the first one
85
- # with the 'capitalize' method and join them together.
86
- camel_string = to_camel_case(snake_str)
87
- return snake_str[0].lower() + camel_string[1:]
190
+ http_response = self._session.send(prepared_request)
88
191
 
192
+ self._print_response(http_response, body=False)
193
+ if iap_token is not None and http_response.status_code in (401, 403):
194
+ raise requests.exceptions.HTTPError("IAP token invalid or expired")
195
+ http_response.raise_for_status()
89
196
 
90
- class KaggleHttpClient(object):
91
- _xsrf_cookie_name = 'XSRF-TOKEN'
92
- _csrf_cookie_name = 'CSRF-TOKEN'
93
- _xsrf_cookies = (_xsrf_cookie_name, _csrf_cookie_name)
94
- _xsrf_header_name = 'X-XSRF-TOKEN'
95
-
96
- def __init__(
97
- self,
98
- env: KaggleEnv = None,
99
- verbose: bool = False,
100
- renew_iap_token=None,
101
- username=None,
102
- password=None,
103
- ):
104
- self._env = env or get_env()
105
- self._signed_in = None
106
- self._endpoint = get_endpoint(self._env)
107
- self._verbose = verbose
108
- self._session = None
109
- self._username = username
110
- self._password = password
111
-
112
- def call(
113
- self,
114
- service_name: str,
115
- request_name: str,
116
- request: KaggleObject,
117
- response_type: Type[KaggleObject],
118
- ):
119
- self._init_session()
120
- http_request = self._prepare_request(service_name, request_name, request)
121
-
122
- # Merge environment settings into session
123
- settings = self._session.merge_environment_settings(http_request.url, {}, None, None, None)
124
- http_response = self._session.send(http_request, **settings)
125
-
126
- response = self._prepare_response(response_type, http_response)
127
- return response
128
-
129
- def _prepare_request(
130
- self, service_name: str, request_name: str, request: KaggleObject
131
- ):
132
- request_url = self._get_request_url(request)
133
- method = request.method()
134
- data = ''
135
- if method == 'GET':
136
- data = request.__class__.to_dict(request, ignore_defaults=False)
137
- if request.endpoint_path():
138
- words = find_words(request.endpoint_path())
139
- list(map(data.pop, [to_lower_camel_case(w) for w in words]))
140
- if len(data) == 0:
141
- data = None
142
- if data:
143
- request_url = f'{request_url}?{urllib.parse.urlencode(clean_data(data))}'
144
- data = ''
145
- self._session.headers.update(
146
- {
147
- 'Accept': 'application/json',
148
- 'Content-Type': 'text/plain',
149
- }
150
- )
151
- elif method == 'POST':
152
- data = request.to_field_map(request, ignore_defaults=True)
153
- if isinstance(data, dict):
154
- fields = request.body_fields()
155
- if fields is not None:
156
- if fields != '*':
157
- data = data[fields]
158
- data = clean_data(data)
159
- if self.requires_form(request):
160
- data, content_type = self.make_form(data)
161
- else:
162
- content_type = 'application/json'
163
- data = json.dumps(data)
164
197
  self._session.headers.update(
165
198
  {
166
- 'Accept': 'application/json',
167
- 'Content-Type': content_type,
199
+ KaggleHttpClient._xsrf_header_name: self._session.cookies[KaggleHttpClient._xsrf_cookie_name],
168
200
  }
169
201
  )
170
- http_request = requests.Request(
171
- method=method,
172
- url=request_url,
173
- data=data,
174
- headers=self._session.headers,
175
- # cookies=self._get_xsrf_cookies(),
176
- auth=self._session.auth,
177
- )
178
- prepared_request = http_request.prepare()
179
- self._print_request(prepared_request)
180
- return prepared_request
181
-
182
- def _get_xsrf_cookies(self):
183
- cookies = requests.cookies.RequestsCookieJar()
184
- for cookie in self._session.cookies:
185
- if cookie.name in KaggleHttpClient._xsrf_cookies:
186
- cookies[cookie.name] = cookie.value
187
- return cookies
188
-
189
- def _prepare_response(self, response_type, http_response):
190
- self._print_response(http_response)
191
- http_response.raise_for_status()
192
- if 'application/json' in http_response.headers['Content-Type']:
193
- resp = http_response.json()
194
- if 'code' in resp and resp['code'] >= 400:
195
- raise requests.exceptions.HTTPError(resp['message'], response=http_response)
196
- if response_type is None: # Method doesn't have a return type
197
- return None
198
- return response_type.prepare_from(http_response)
199
-
200
- def _print_request(self, request):
201
- if not self._verbose:
202
- return
203
- self._print('---------------------Request----------------------')
204
- self._print(
205
- f'{request.method} {request.url}\n{_headers_to_str(request.headers)}\n\n{request.body}'
206
- )
207
- self._print('--------------------------------------------------')
208
-
209
- def _print_response(self, response, body=True):
210
- if not self._verbose:
211
- return
212
- self._print('---------------------Response---------------------')
213
- self._print(f'{response.status_code}\n{_headers_to_str(response.headers)}')
214
- if body:
215
- self._print(f'\n{response.text}')
216
- self._print('--------------------------------------------------')
217
-
218
- def _print(self, message: str):
219
- if self._verbose:
220
- print(message)
221
-
222
- def __enter__(self):
223
- self._init_session()
224
- return self
225
-
226
- def __exit__(self, exc_type, exc_value, tb):
227
- if self._session is not None:
228
- self._session.close()
229
-
230
- def _init_session(self):
231
- if self._session is not None:
232
- return self._session
233
-
234
- self._session = requests.Session()
235
- self._session.headers.update(
236
- {
237
- 'User-Agent': 'kaggle-api/v1.7.0', # Was: V2
238
- 'Content-Type': 'application/x-www-form-urlencoded', # Was: /json
239
- }
240
- )
241
-
242
- iap_token = self._get_iap_token_if_required()
243
- if iap_token is not None:
244
- self._session.headers.update(
245
- {
246
- # https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_proxy-authorization_header
247
- 'Proxy-Authorization': f'Bearer {iap_token}',
248
- }
249
- )
250
-
251
- self._try_fill_auth()
252
- # self._fill_xsrf_token(iap_token) # TODO Make this align with original handler.
253
-
254
- def _get_iap_token_if_required(self):
255
- if self._env not in (KaggleEnv.STAGING, KaggleEnv.ADMIN):
256
- return None
257
- iap_token = os.getenv('KAGGLE_IAP_TOKEN')
258
- if iap_token is None:
259
- raise Exception(f'Must set KAGGLE_IAP_TOKEN to access "{self._endpoint}"')
260
- return iap_token
261
-
262
- def _fill_xsrf_token(self, iap_token):
263
- initial_get_request = requests.Request(
264
- method='GET',
265
- url=self._endpoint,
266
- headers=self._session.headers,
267
- auth=self._session.auth,
268
- )
269
- prepared_request = initial_get_request.prepare()
270
- self._print_request(prepared_request)
271
-
272
- http_response = self._session.send(prepared_request)
273
-
274
- self._print_response(http_response, body=False)
275
- if iap_token is not None and http_response.status_code in (401, 403):
276
- raise requests.exceptions.HTTPError('IAP token invalid or expired')
277
- http_response.raise_for_status()
278
-
279
- self._session.headers.update(
280
- {
281
- KaggleHttpClient._xsrf_header_name: self._session.cookies[
282
- KaggleHttpClient._xsrf_cookie_name
283
- ],
284
- }
285
- )
286
-
287
- class BearerAuth(requests.auth.AuthBase):
288
-
289
- def __init__(self, token):
290
- self.token = token
291
-
292
- def __call__(self, r):
293
- r.headers['Authorization'] = f'Bearer {self.token}'
294
- return r
295
-
296
- def _try_fill_auth(self):
297
- if self._signed_in is not None:
298
- return
299
202
 
300
- api_token = os.getenv('KAGGLE_API_TOKEN')
301
- if api_token is not None:
302
- self._session.auth = KaggleHttpClient.BearerAuth(api_token)
303
- self._signed_in = True
304
- return
305
-
306
- if self._username and self._password:
307
- apikey_creds = self._username, self._password
308
- else:
309
- apikey_creds = _get_apikey_creds()
310
- if apikey_creds is not None:
311
- self._session.auth = apikey_creds
312
- self._signed_in = True
313
- return
314
-
315
- self._signed_in = False
316
-
317
- def _get_request_url(self, request):
318
- return f'{self._endpoint}{request.endpoint()}'
319
-
320
- @staticmethod
321
- def make_form(fields):
322
- body = BytesIO()
323
- boundary = binascii.hexlify(os.urandom(16)).decode()
324
- writer = codecs.lookup('utf-8')[3]
203
+ def build_start_oauth_url(
204
+ self,
205
+ client_id: str,
206
+ redirect_uri: str,
207
+ scope: list[str],
208
+ state: str,
209
+ code_challenge: str,
210
+ ) -> str:
211
+ params = {
212
+ "response_type": "code",
213
+ "client_id": client_id,
214
+ "redirect_uri": redirect_uri,
215
+ "scope": " ".join(scope),
216
+ "state": state,
217
+ "code_challenge": code_challenge,
218
+ "code_challenge_method": "S256",
219
+ "response_type": "code",
220
+ "response_mode": "query",
221
+ }
222
+ auth_url = f"{self.get_non_api_endpoint()}/api/v1/oauth2/authorize"
223
+ query_string = urllib.parse.urlencode(params, quote_via=urllib.parse.quote_plus)
224
+ return f"{auth_url}?{query_string}"
325
225
 
326
- for field in fields.items():
327
- field = RequestField.from_tuples(*field)
328
- body.write(f'--{boundary}\r\n'.encode('latin-1'))
226
+ def get_oauth_default_redirect_url(self) -> str:
227
+ return f"{self.get_non_api_endpoint()}/account/api/oauth/token"
329
228
 
330
- writer(body).write(field.render_headers())
331
- data = field.data
229
+ def get_non_api_endpoint(self) -> str:
230
+ return "https://www.kaggle.com" if self._env == KaggleEnv.PROD else self._endpoint
332
231
 
333
- if isinstance(data, int):
334
- data = str(data)
232
+ class BearerAuth(requests.auth.AuthBase):
335
233
 
336
- if isinstance(data, str):
337
- writer(body).write(data)
338
- else:
339
- body.write(data)
234
+ def __init__(self, token):
235
+ self.token = token
340
236
 
341
- body.write(b'\r\n')
237
+ def __call__(self, r):
238
+ r.headers["Authorization"] = f"Bearer {self.token}"
239
+ return r
342
240
 
343
- body.write(f'--{boundary}--\r\n'.encode('latin-1'))
241
+ def _try_fill_auth(self):
242
+ if self._signed_in is not None:
243
+ return
344
244
 
345
- content_type = f'multipart/form-data; boundary={boundary}'
245
+ if self._api_token is None:
246
+ (api_token, _) = get_access_token_from_env()
247
+ self._api_token = api_token
346
248
 
347
- return body.getvalue(), content_type
249
+ if self._api_token is not None:
250
+ self._session.auth = KaggleHttpClient.BearerAuth(self._api_token)
251
+ self._signed_in = True
252
+ return
348
253
 
349
- @staticmethod
350
- def requires_form(request):
351
- return type(request).__name__ in REQUESTS_REQUIRING_FORMS
254
+ if self._username and self._password:
255
+ apikey_creds = self._username, self._password
256
+ else:
257
+ apikey_creds = _get_apikey_creds()
258
+ if apikey_creds is not None:
259
+ self._session.auth = apikey_creds
260
+ self._signed_in = True
261
+ return
262
+
263
+ self._signed_in = False
264
+
265
+ def _get_request_url(self, service_name: str, request_name: str):
266
+ # On prod, API endpoints are served under https://api.kaggle.com/v1,
267
+ # but on staging/admin/local, they are served under http://localhost/api/v1.
268
+ base_url = self._endpoint if self._env == KaggleEnv.PROD else f"{self._endpoint}/api"
269
+ return f"{base_url}/v1/{service_name}/{request_name}"