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.
- kaggle/__init__.py +10 -6
- kaggle/api/kaggle_api.py +574 -598
- kaggle/api/kaggle_api_extended.py +5251 -4769
- kaggle/cli.py +1335 -1585
- kaggle/models/api_blob_type.py +3 -3
- kaggle/models/dataset_column.py +165 -174
- kaggle/models/dataset_new_request.py +83 -41
- kaggle/models/dataset_new_version_request.py +32 -25
- kaggle/models/dataset_update_settings_request.py +35 -27
- kaggle/models/kaggle_models_extended.py +169 -172
- kaggle/models/kernel_push_request.py +66 -49
- kaggle/models/model_instance_new_version_request.py +10 -18
- kaggle/models/model_instance_update_request.py +103 -34
- kaggle/models/model_new_instance_request.py +138 -41
- kaggle/models/model_new_request.py +35 -27
- kaggle/models/model_update_request.py +32 -25
- kaggle/models/start_blob_upload_request.py +192 -195
- kaggle/models/start_blob_upload_response.py +98 -98
- kaggle/models/upload_file.py +114 -120
- kaggle/test/test_authenticate.py +23 -23
- {kaggle-1.7.4.5.dist-info → kaggle-1.8.2.dist-info}/METADATA +12 -15
- kaggle-1.8.2.dist-info/RECORD +148 -0
- kagglesdk/__init__.py +5 -1
- kagglesdk/benchmarks/services/__init__.py +0 -0
- kagglesdk/benchmarks/services/benchmarks_api_service.py +19 -0
- kagglesdk/benchmarks/types/__init__.py +0 -0
- kagglesdk/benchmarks/types/benchmark_types.py +307 -0
- kagglesdk/benchmarks/types/benchmarks_api_service.py +243 -0
- kagglesdk/blobs/services/blob_api_service.py +1 -1
- kagglesdk/blobs/types/blob_api_service.py +2 -2
- kagglesdk/common/services/__init__.py +0 -0
- kagglesdk/common/services/operations_service.py +46 -0
- kagglesdk/common/types/file_download.py +1 -1
- kagglesdk/common/types/http_redirect.py +1 -1
- kagglesdk/common/types/operations.py +194 -0
- kagglesdk/common/types/operations_service.py +48 -0
- kagglesdk/community/__init__.py +0 -0
- kagglesdk/community/types/__init__.py +0 -0
- kagglesdk/community/types/content_enums.py +44 -0
- kagglesdk/community/types/organization.py +410 -0
- kagglesdk/competitions/services/competition_api_service.py +49 -12
- kagglesdk/competitions/types/competition.py +14 -0
- kagglesdk/competitions/types/competition_api_service.py +1639 -1275
- kagglesdk/competitions/types/search_competitions.py +28 -0
- kagglesdk/datasets/databundles/__init__.py +0 -0
- kagglesdk/datasets/databundles/types/__init__.py +0 -0
- kagglesdk/datasets/databundles/types/databundle_api_types.py +540 -0
- kagglesdk/datasets/services/dataset_api_service.py +39 -14
- kagglesdk/datasets/types/dataset_api_service.py +554 -300
- kagglesdk/datasets/types/dataset_enums.py +21 -0
- kagglesdk/datasets/types/dataset_service.py +145 -0
- kagglesdk/datasets/types/dataset_types.py +74 -74
- kagglesdk/datasets/types/search_datasets.py +6 -0
- kagglesdk/discussions/__init__.py +0 -0
- kagglesdk/discussions/types/__init__.py +0 -0
- kagglesdk/discussions/types/search_discussions.py +43 -0
- kagglesdk/discussions/types/writeup_enums.py +11 -0
- kagglesdk/education/services/education_api_service.py +1 -1
- kagglesdk/education/types/education_api_service.py +1 -1
- kagglesdk/kaggle_client.py +46 -23
- kagglesdk/kaggle_creds.py +148 -0
- kagglesdk/kaggle_env.py +89 -25
- kagglesdk/kaggle_http_client.py +224 -306
- kagglesdk/kaggle_oauth.py +200 -0
- kagglesdk/kaggle_object.py +286 -293
- kagglesdk/kernels/services/kernels_api_service.py +46 -9
- kagglesdk/kernels/types/kernels_api_service.py +635 -159
- kagglesdk/kernels/types/kernels_enums.py +6 -0
- kagglesdk/kernels/types/search_kernels.py +6 -0
- kagglesdk/licenses/__init__.py +0 -0
- kagglesdk/licenses/types/__init__.py +0 -0
- kagglesdk/licenses/types/licenses_types.py +182 -0
- kagglesdk/models/services/model_api_service.py +41 -17
- kagglesdk/models/types/model_api_service.py +987 -637
- kagglesdk/models/types/model_enums.py +8 -0
- kagglesdk/models/types/model_service.py +71 -71
- kagglesdk/models/types/model_types.py +1057 -5
- kagglesdk/models/types/search_models.py +8 -0
- kagglesdk/search/__init__.py +0 -0
- kagglesdk/search/services/__init__.py +0 -0
- kagglesdk/search/services/search_api_service.py +19 -0
- kagglesdk/search/types/__init__.py +0 -0
- kagglesdk/search/types/search_api_service.py +2435 -0
- kagglesdk/search/types/search_content_shared.py +50 -0
- kagglesdk/search/types/search_enums.py +45 -0
- kagglesdk/search/types/search_service.py +303 -0
- kagglesdk/security/services/iam_service.py +31 -0
- kagglesdk/security/services/oauth_service.py +27 -1
- kagglesdk/security/types/authentication.py +63 -63
- kagglesdk/security/types/iam_service.py +496 -0
- kagglesdk/security/types/oauth_service.py +797 -10
- kagglesdk/security/types/roles.py +8 -0
- kagglesdk/security/types/security_types.py +159 -0
- kagglesdk/test/__init__.py +0 -0
- kagglesdk/test/test_client.py +20 -22
- kagglesdk/users/services/account_service.py +13 -1
- kagglesdk/users/services/group_api_service.py +31 -0
- kagglesdk/users/types/account_service.py +169 -28
- kagglesdk/users/types/group_api_service.py +315 -0
- kagglesdk/users/types/group_types.py +165 -0
- kagglesdk/users/types/groups_enum.py +8 -0
- kagglesdk/users/types/progression_service.py +9 -0
- kagglesdk/users/types/search_users.py +23 -0
- kagglesdk/users/types/user_avatar.py +226 -0
- kaggle/configuration.py +0 -206
- kaggle-1.7.4.5.dist-info/RECORD +0 -98
- {kaggle-1.7.4.5.dist-info → kaggle-1.8.2.dist-info}/WHEEL +0 -0
- {kaggle-1.7.4.5.dist-info → kaggle-1.8.2.dist-info}/entry_points.txt +0 -0
- {kaggle-1.7.4.5.dist-info → kaggle-1.8.2.dist-info}/licenses/LICENSE.txt +0 -0
- {kaggle/test → kagglesdk/benchmarks}/__init__.py +0 -0
kagglesdk/kaggle_http_client.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
30
|
+
return "\n".join(f"{k}: {v}" for k, v in headers.items())
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
def _get_apikey_creds():
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
38
|
+
kaggle_json = None
|
|
39
|
+
with open(apikey_filename) as apikey_file:
|
|
40
|
+
kaggle_json = apikey_file.read()
|
|
69
41
|
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
327
|
-
|
|
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
|
-
|
|
331
|
-
|
|
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
|
-
|
|
334
|
-
data = str(data)
|
|
232
|
+
class BearerAuth(requests.auth.AuthBase):
|
|
335
233
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
else:
|
|
339
|
-
body.write(data)
|
|
234
|
+
def __init__(self, token):
|
|
235
|
+
self.token = token
|
|
340
236
|
|
|
341
|
-
|
|
237
|
+
def __call__(self, r):
|
|
238
|
+
r.headers["Authorization"] = f"Bearer {self.token}"
|
|
239
|
+
return r
|
|
342
240
|
|
|
343
|
-
|
|
241
|
+
def _try_fill_auth(self):
|
|
242
|
+
if self._signed_in is not None:
|
|
243
|
+
return
|
|
344
244
|
|
|
345
|
-
|
|
245
|
+
if self._api_token is None:
|
|
246
|
+
(api_token, _) = get_access_token_from_env()
|
|
247
|
+
self._api_token = api_token
|
|
346
248
|
|
|
347
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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}"
|