kaggle 1.8.2__py3-none-any.whl → 1.8.4__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 +1 -1
- kaggle/api/kaggle_api_extended.py +206 -75
- kaggle/cli.py +36 -28
- kaggle/models/upload_file.py +4 -4
- {kaggle-1.8.2.dist-info → kaggle-1.8.4.dist-info}/METADATA +66 -75
- kaggle-1.8.4.dist-info/RECORD +15 -0
- {kaggle-1.8.2.dist-info → kaggle-1.8.4.dist-info}/WHEEL +1 -1
- kaggle/models/api_blob_type.py +0 -4
- kaggle/models/dataset_column.py +0 -228
- kaggle/models/dataset_new_request.py +0 -443
- kaggle/models/dataset_new_version_request.py +0 -319
- kaggle/models/dataset_update_settings_request.py +0 -344
- kaggle/models/kernel_push_request.py +0 -608
- kaggle/models/model_instance_new_version_request.py +0 -145
- kaggle/models/model_instance_update_request.py +0 -451
- kaggle/models/model_new_instance_request.py +0 -552
- kaggle/models/model_new_request.py +0 -329
- kaggle/models/model_update_request.py +0 -300
- kaggle/models/start_blob_upload_request.py +0 -240
- kaggle/models/start_blob_upload_response.py +0 -142
- kaggle-1.8.2.dist-info/RECORD +0 -148
- kagglesdk/LICENSE +0 -201
- kagglesdk/__init__.py +0 -6
- kagglesdk/admin/__init__.py +0 -0
- kagglesdk/admin/services/__init__.py +0 -0
- kagglesdk/admin/services/inbox_file_service.py +0 -22
- kagglesdk/admin/types/__init__.py +0 -0
- kagglesdk/admin/types/inbox_file_service.py +0 -74
- kagglesdk/benchmarks/__init__.py +0 -0
- kagglesdk/benchmarks/services/__init__.py +0 -0
- kagglesdk/benchmarks/services/benchmarks_api_service.py +0 -19
- kagglesdk/benchmarks/types/__init__.py +0 -0
- kagglesdk/benchmarks/types/benchmark_types.py +0 -307
- kagglesdk/benchmarks/types/benchmarks_api_service.py +0 -243
- kagglesdk/blobs/__init__.py +0 -0
- kagglesdk/blobs/services/__init__.py +0 -0
- kagglesdk/blobs/services/blob_api_service.py +0 -25
- kagglesdk/blobs/types/__init__.py +0 -0
- kagglesdk/blobs/types/blob_api_service.py +0 -177
- kagglesdk/common/__init__.py +0 -0
- kagglesdk/common/services/__init__.py +0 -0
- kagglesdk/common/services/operations_service.py +0 -46
- kagglesdk/common/types/__init__.py +0 -0
- kagglesdk/common/types/file_download.py +0 -102
- kagglesdk/common/types/http_redirect.py +0 -105
- kagglesdk/common/types/operations.py +0 -194
- kagglesdk/common/types/operations_service.py +0 -48
- kagglesdk/community/__init__.py +0 -0
- kagglesdk/community/types/__init__.py +0 -0
- kagglesdk/community/types/content_enums.py +0 -44
- kagglesdk/community/types/organization.py +0 -410
- kagglesdk/competitions/__init__.py +0 -0
- kagglesdk/competitions/services/__init__.py +0 -0
- kagglesdk/competitions/services/competition_api_service.py +0 -178
- kagglesdk/competitions/types/__init__.py +0 -0
- kagglesdk/competitions/types/competition.py +0 -14
- kagglesdk/competitions/types/competition_api_service.py +0 -2393
- kagglesdk/competitions/types/competition_enums.py +0 -53
- kagglesdk/competitions/types/search_competitions.py +0 -28
- kagglesdk/competitions/types/submission_status.py +0 -9
- kagglesdk/datasets/__init__.py +0 -0
- kagglesdk/datasets/databundles/__init__.py +0 -0
- kagglesdk/datasets/databundles/types/__init__.py +0 -0
- kagglesdk/datasets/databundles/types/databundle_api_types.py +0 -540
- kagglesdk/datasets/services/__init__.py +0 -0
- kagglesdk/datasets/services/dataset_api_service.py +0 -195
- kagglesdk/datasets/types/__init__.py +0 -0
- kagglesdk/datasets/types/dataset_api_service.py +0 -3047
- kagglesdk/datasets/types/dataset_enums.py +0 -103
- kagglesdk/datasets/types/dataset_service.py +0 -145
- kagglesdk/datasets/types/dataset_types.py +0 -646
- kagglesdk/datasets/types/search_datasets.py +0 -6
- kagglesdk/discussions/__init__.py +0 -0
- kagglesdk/discussions/types/__init__.py +0 -0
- kagglesdk/discussions/types/search_discussions.py +0 -43
- kagglesdk/discussions/types/writeup_enums.py +0 -11
- kagglesdk/education/__init__.py +0 -0
- kagglesdk/education/services/__init__.py +0 -0
- kagglesdk/education/services/education_api_service.py +0 -19
- kagglesdk/education/types/__init__.py +0 -0
- kagglesdk/education/types/education_api_service.py +0 -248
- kagglesdk/education/types/education_service.py +0 -139
- kagglesdk/kaggle_client.py +0 -101
- kagglesdk/kaggle_creds.py +0 -148
- kagglesdk/kaggle_env.py +0 -104
- kagglesdk/kaggle_http_client.py +0 -269
- kagglesdk/kaggle_oauth.py +0 -200
- kagglesdk/kaggle_object.py +0 -344
- kagglesdk/kernels/__init__.py +0 -0
- kagglesdk/kernels/services/__init__.py +0 -0
- kagglesdk/kernels/services/kernels_api_service.py +0 -146
- kagglesdk/kernels/types/__init__.py +0 -0
- kagglesdk/kernels/types/kernels_api_service.py +0 -2451
- kagglesdk/kernels/types/kernels_enums.py +0 -39
- kagglesdk/kernels/types/search_kernels.py +0 -6
- kagglesdk/licenses/__init__.py +0 -0
- kagglesdk/licenses/types/__init__.py +0 -0
- kagglesdk/licenses/types/licenses_types.py +0 -182
- kagglesdk/models/__init__.py +0 -0
- kagglesdk/models/services/__init__.py +0 -0
- kagglesdk/models/services/model_api_service.py +0 -280
- kagglesdk/models/services/model_service.py +0 -19
- kagglesdk/models/types/__init__.py +0 -0
- kagglesdk/models/types/model_api_service.py +0 -4069
- kagglesdk/models/types/model_enums.py +0 -68
- kagglesdk/models/types/model_service.py +0 -275
- kagglesdk/models/types/model_types.py +0 -1338
- kagglesdk/models/types/search_models.py +0 -8
- kagglesdk/search/__init__.py +0 -0
- kagglesdk/search/services/__init__.py +0 -0
- kagglesdk/search/services/search_api_service.py +0 -19
- kagglesdk/search/types/__init__.py +0 -0
- kagglesdk/search/types/search_api_service.py +0 -2435
- kagglesdk/search/types/search_content_shared.py +0 -50
- kagglesdk/search/types/search_enums.py +0 -45
- kagglesdk/search/types/search_service.py +0 -303
- kagglesdk/security/__init__.py +0 -0
- kagglesdk/security/services/__init__.py +0 -0
- kagglesdk/security/services/iam_service.py +0 -31
- kagglesdk/security/services/oauth_service.py +0 -58
- kagglesdk/security/types/__init__.py +0 -0
- kagglesdk/security/types/authentication.py +0 -171
- kagglesdk/security/types/iam_service.py +0 -496
- kagglesdk/security/types/oauth_service.py +0 -1181
- kagglesdk/security/types/roles.py +0 -8
- kagglesdk/security/types/security_types.py +0 -159
- kagglesdk/test/__init__.py +0 -0
- kagglesdk/test/test_client.py +0 -41
- kagglesdk/users/__init__.py +0 -0
- kagglesdk/users/services/__init__.py +0 -0
- kagglesdk/users/services/account_service.py +0 -31
- kagglesdk/users/services/group_api_service.py +0 -31
- kagglesdk/users/types/__init__.py +0 -0
- kagglesdk/users/types/account_service.py +0 -345
- kagglesdk/users/types/group_api_service.py +0 -315
- kagglesdk/users/types/group_types.py +0 -165
- kagglesdk/users/types/groups_enum.py +0 -8
- kagglesdk/users/types/progression_service.py +0 -9
- kagglesdk/users/types/search_users.py +0 -23
- kagglesdk/users/types/user_avatar.py +0 -226
- kagglesdk/users/types/users_enums.py +0 -22
- {kaggle-1.8.2.dist-info → kaggle-1.8.4.dist-info}/entry_points.txt +0 -0
- {kaggle-1.8.2.dist-info → kaggle-1.8.4.dist-info}/licenses/LICENSE.txt +0 -0
kagglesdk/kaggle_env.py
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
KAGGLE_NOTEBOOK_ENV_VAR_NAME = "KAGGLE_KERNEL_RUN_TYPE"
|
|
7
|
-
KAGGLE_DATA_PROXY_URL_ENV_VAR_NAME = "KAGGLE_DATA_PROXY_URL"
|
|
8
|
-
KAGGLE_API_V1_TOKEN_PATH = "KAGGLE_API_V1_TOKEN"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def get_logger():
|
|
12
|
-
return logging.getLogger(__name__)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class KaggleEnv(Enum):
|
|
16
|
-
LOCAL = 0 # localhost
|
|
17
|
-
STAGING = 1 # staging.kaggle.com
|
|
18
|
-
ADMIN = 2 # admin.kaggle.com
|
|
19
|
-
QA = 3 # qa.kaggle.com
|
|
20
|
-
PROD = 4 # api.kaggle.com
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
_env_to_endpoint = {
|
|
24
|
-
KaggleEnv.LOCAL: "http://localhost",
|
|
25
|
-
KaggleEnv.STAGING: "https://staging.kaggle.com",
|
|
26
|
-
KaggleEnv.ADMIN: "https://admin.kaggle.com",
|
|
27
|
-
KaggleEnv.QA: "https://qa.kaggle.com",
|
|
28
|
-
KaggleEnv.PROD: "https://api.kaggle.com",
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def get_endpoint(env: KaggleEnv):
|
|
33
|
-
return _env_to_endpoint[env]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def get_env():
|
|
37
|
-
env = os.getenv("KAGGLE_API_ENVIRONMENT")
|
|
38
|
-
if env is None or env == "PROD":
|
|
39
|
-
return KaggleEnv.PROD
|
|
40
|
-
if env == "LOCALHOST":
|
|
41
|
-
return KaggleEnv.LOCAL
|
|
42
|
-
if env == "ADMIN":
|
|
43
|
-
return KaggleEnv.ADMIN
|
|
44
|
-
if env == "STAGING":
|
|
45
|
-
return KaggleEnv.STAGING
|
|
46
|
-
if env == "QA":
|
|
47
|
-
return KaggleEnv.QA
|
|
48
|
-
raise Exception(f'Unrecognized value in KAGGLE_API_ENVIRONMENT: "{env}"')
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def is_in_kaggle_notebook() -> bool:
|
|
52
|
-
if os.getenv(KAGGLE_NOTEBOOK_ENV_VAR_NAME) is not None:
|
|
53
|
-
if os.getenv(KAGGLE_DATA_PROXY_URL_ENV_VAR_NAME) is None:
|
|
54
|
-
# Missing endpoint for the Jwt client
|
|
55
|
-
get_logger().warning(
|
|
56
|
-
"Can't use the Kaggle Cache. "
|
|
57
|
-
f"The '{KAGGLE_DATA_PROXY_URL_ENV_VAR_NAME}' environment variable is not set."
|
|
58
|
-
)
|
|
59
|
-
return False
|
|
60
|
-
return True
|
|
61
|
-
return False
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _get_access_token_from_file(path):
|
|
65
|
-
if not path:
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
token_path = Path(path)
|
|
69
|
-
if not token_path.exists():
|
|
70
|
-
return None
|
|
71
|
-
|
|
72
|
-
token_value = token_path.read_text().strip()
|
|
73
|
-
if not token_value:
|
|
74
|
-
return None
|
|
75
|
-
|
|
76
|
-
get_logger().debug(f'Using access token from file: "{path}"')
|
|
77
|
-
return token_value
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def get_access_token_from_env():
|
|
81
|
-
if is_in_kaggle_notebook():
|
|
82
|
-
token = _get_access_token_from_file(os.environ.get(KAGGLE_API_V1_TOKEN_PATH))
|
|
83
|
-
if token:
|
|
84
|
-
return (token, KAGGLE_API_V1_TOKEN_PATH)
|
|
85
|
-
|
|
86
|
-
access_token = os.environ.get("KAGGLE_API_TOKEN")
|
|
87
|
-
if access_token:
|
|
88
|
-
if Path(access_token).exists():
|
|
89
|
-
return (_get_access_token_from_file(access_token), "KAGGLE_API_TOKEN")
|
|
90
|
-
get_logger().debug(
|
|
91
|
-
"Using access token from KAGGLE_API_TOKEN environment variable"
|
|
92
|
-
)
|
|
93
|
-
return (access_token, "KAGGLE_API_TOKEN")
|
|
94
|
-
|
|
95
|
-
access_token = _get_access_token_from_file(os.path.expanduser("~/.kaggle/access_token"))
|
|
96
|
-
if access_token:
|
|
97
|
-
return (access_token, "access_token")
|
|
98
|
-
|
|
99
|
-
# Check ".txt" as well in case Windows users create the file with this extension.
|
|
100
|
-
access_token = _get_access_token_from_file(os.path.expanduser("~/.kaggle/access_token.txt"))
|
|
101
|
-
if access_token:
|
|
102
|
-
return (access_token, "access_token")
|
|
103
|
-
|
|
104
|
-
return (None, None)
|
kagglesdk/kaggle_http_client.py
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import binascii
|
|
2
|
-
import codecs
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import urllib.parse
|
|
6
|
-
from io import BytesIO
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
import requests
|
|
10
|
-
from urllib3.fields import RequestField
|
|
11
|
-
|
|
12
|
-
from kagglesdk.kaggle_env import (
|
|
13
|
-
get_endpoint,
|
|
14
|
-
get_env,
|
|
15
|
-
get_access_token_from_env,
|
|
16
|
-
KaggleEnv,
|
|
17
|
-
)
|
|
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
|
|
21
|
-
from typing import Type
|
|
22
|
-
|
|
23
|
-
# TODO (http://b/354237483) Generate the client from the existing one.
|
|
24
|
-
# This was created from kaggle_api_client.py, prior to recent changes to
|
|
25
|
-
# auth handling. The new client requires KAGGLE_API_TOKEN, so it is not
|
|
26
|
-
# currently usable by the CLI.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _headers_to_str(headers):
|
|
30
|
-
return "\n".join(f"{k}: {v}" for k, v in headers.items())
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _get_apikey_creds():
|
|
34
|
-
apikey_filename = os.path.expanduser("~/.kaggle/kaggle.json")
|
|
35
|
-
if not os.path.exists(apikey_filename):
|
|
36
|
-
return None
|
|
37
|
-
|
|
38
|
-
kaggle_json = None
|
|
39
|
-
with open(apikey_filename) as apikey_file:
|
|
40
|
-
kaggle_json = apikey_file.read()
|
|
41
|
-
|
|
42
|
-
if not kaggle_json or not kaggle_json.strip():
|
|
43
|
-
return None
|
|
44
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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)
|
|
189
|
-
|
|
190
|
-
http_response = self._session.send(prepared_request)
|
|
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()
|
|
196
|
-
|
|
197
|
-
self._session.headers.update(
|
|
198
|
-
{
|
|
199
|
-
KaggleHttpClient._xsrf_header_name: self._session.cookies[KaggleHttpClient._xsrf_cookie_name],
|
|
200
|
-
}
|
|
201
|
-
)
|
|
202
|
-
|
|
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}"
|
|
225
|
-
|
|
226
|
-
def get_oauth_default_redirect_url(self) -> str:
|
|
227
|
-
return f"{self.get_non_api_endpoint()}/account/api/oauth/token"
|
|
228
|
-
|
|
229
|
-
def get_non_api_endpoint(self) -> str:
|
|
230
|
-
return "https://www.kaggle.com" if self._env == KaggleEnv.PROD else self._endpoint
|
|
231
|
-
|
|
232
|
-
class BearerAuth(requests.auth.AuthBase):
|
|
233
|
-
|
|
234
|
-
def __init__(self, token):
|
|
235
|
-
self.token = token
|
|
236
|
-
|
|
237
|
-
def __call__(self, r):
|
|
238
|
-
r.headers["Authorization"] = f"Bearer {self.token}"
|
|
239
|
-
return r
|
|
240
|
-
|
|
241
|
-
def _try_fill_auth(self):
|
|
242
|
-
if self._signed_in is not None:
|
|
243
|
-
return
|
|
244
|
-
|
|
245
|
-
if self._api_token is None:
|
|
246
|
-
(api_token, _) = get_access_token_from_env()
|
|
247
|
-
self._api_token = api_token
|
|
248
|
-
|
|
249
|
-
if self._api_token is not None:
|
|
250
|
-
self._session.auth = KaggleHttpClient.BearerAuth(self._api_token)
|
|
251
|
-
self._signed_in = True
|
|
252
|
-
return
|
|
253
|
-
|
|
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}"
|
kagglesdk/kaggle_oauth.py
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import hashlib
|
|
3
|
-
import http.server
|
|
4
|
-
import logging
|
|
5
|
-
import os
|
|
6
|
-
import platform
|
|
7
|
-
import random
|
|
8
|
-
import secrets
|
|
9
|
-
import socketserver
|
|
10
|
-
import uuid
|
|
11
|
-
import urllib.parse
|
|
12
|
-
import webbrowser
|
|
13
|
-
from datetime import datetime, timedelta, timezone
|
|
14
|
-
from kagglesdk.kaggle_client import KaggleClient
|
|
15
|
-
from kagglesdk.kaggle_creds import KaggleCredentials
|
|
16
|
-
from kagglesdk.security.types.oauth_service import ExchangeOAuthTokenRequest
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class KaggleOAuth:
|
|
20
|
-
OAUTH_CLIENT_ID = "kagglesdk"
|
|
21
|
-
|
|
22
|
-
def __init__(self, client: KaggleClient):
|
|
23
|
-
self._client = client
|
|
24
|
-
self._http_client = client.http_client()
|
|
25
|
-
self._server_running = False
|
|
26
|
-
self._creds = None
|
|
27
|
-
self._logger = logging.getLogger(__name__)
|
|
28
|
-
|
|
29
|
-
class OAuthState:
|
|
30
|
-
def __init__(self):
|
|
31
|
-
self.state = str(uuid.uuid4())
|
|
32
|
-
self.code_verifier = KaggleOAuth.OAuthState._generate_code_verifier()
|
|
33
|
-
self.code_challenge = KaggleOAuth.OAuthState._generate_code_challenge(self.code_verifier)
|
|
34
|
-
|
|
35
|
-
def _generate_state(length: int = 32):
|
|
36
|
-
return secrets.token_urlsafe(length)
|
|
37
|
-
|
|
38
|
-
def _generate_code_verifier(length: int = 64) -> str:
|
|
39
|
-
if not 42 <= length <= 128:
|
|
40
|
-
raise ValueError("Code verifier length must be between 42 and 128 characters.")
|
|
41
|
-
return secrets.token_urlsafe(length)
|
|
42
|
-
|
|
43
|
-
def _generate_code_challenge(code_verifier: str) -> str:
|
|
44
|
-
code_verifier_bytes = code_verifier.encode("utf-8")
|
|
45
|
-
code_challenge_bytes = hashlib.sha256(code_verifier_bytes).digest()
|
|
46
|
-
code_challenge_base64 = base64.urlsafe_b64encode(code_challenge_bytes).decode("utf-8")
|
|
47
|
-
return code_challenge_base64
|
|
48
|
-
|
|
49
|
-
class OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
|
|
50
|
-
def __init__(
|
|
51
|
-
self,
|
|
52
|
-
*args,
|
|
53
|
-
oauth: "KaggleOAuth" = None,
|
|
54
|
-
oauth_state: "KaggleOAuth.OAuthState" = None,
|
|
55
|
-
on_success=None,
|
|
56
|
-
logger=None,
|
|
57
|
-
**kwargs,
|
|
58
|
-
):
|
|
59
|
-
self._oauth = oauth
|
|
60
|
-
self._oauth_state = oauth_state
|
|
61
|
-
self._on_success = on_success
|
|
62
|
-
self._logger = logger
|
|
63
|
-
super().__init__(*args, **kwargs)
|
|
64
|
-
|
|
65
|
-
def do_GET(self):
|
|
66
|
-
if self.path == "/favicon.ico":
|
|
67
|
-
return
|
|
68
|
-
try:
|
|
69
|
-
self._handle_oauth_callback()
|
|
70
|
-
finally:
|
|
71
|
-
self._stop_server()
|
|
72
|
-
|
|
73
|
-
def _handle_oauth_callback(self):
|
|
74
|
-
parsed_url = urllib.parse.urlparse(self.path)
|
|
75
|
-
query_params = urllib.parse.parse_qs(parsed_url.query)
|
|
76
|
-
if "code" in query_params and "state" in query_params:
|
|
77
|
-
code = query_params["code"][0]
|
|
78
|
-
state = query_params["state"][0]
|
|
79
|
-
self._logger.debug(f"\nReceived OAuth Callback:")
|
|
80
|
-
self._logger.debug(f" code : {code}")
|
|
81
|
-
self._logger.debug(f" state: {state}")
|
|
82
|
-
if state == self._oauth_state.state:
|
|
83
|
-
self.send_response(200)
|
|
84
|
-
self.send_header("Content-type", "text/html")
|
|
85
|
-
self.end_headers()
|
|
86
|
-
self.wfile.write(
|
|
87
|
-
b"<html><body><h1>Authentication Successful!</h1><p>You can close this window.</p></body></html>"
|
|
88
|
-
)
|
|
89
|
-
self._on_success(code)
|
|
90
|
-
else:
|
|
91
|
-
self._logger.error(f"Invalid state! Expected: {self._oauth_state.state}, Received: {state}")
|
|
92
|
-
self.send_response(400)
|
|
93
|
-
self.send_header("Content-type", "text/html")
|
|
94
|
-
self.end_headers()
|
|
95
|
-
self.wfile.write(b"<html><body><h1>Authentication Failed!</h1></body></html>")
|
|
96
|
-
else:
|
|
97
|
-
self._logger.debug(f"\nReceived Invalid OAuth Callback: {self.path}")
|
|
98
|
-
self.send_response(400)
|
|
99
|
-
self.send_header("Content-type", "text/html")
|
|
100
|
-
self.end_headers()
|
|
101
|
-
self.wfile.write(
|
|
102
|
-
b"<html><body><h1>Authentication Failed!</h1><p>Invalid callback parameters.</p></body></html>"
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
def _stop_server(self):
|
|
106
|
-
self._oauth.stop_server()
|
|
107
|
-
|
|
108
|
-
@staticmethod
|
|
109
|
-
def _can_open_browser():
|
|
110
|
-
if platform.system() in ["Windows", "Darwin"]:
|
|
111
|
-
return True # Assume GUI on Windows/Mac
|
|
112
|
-
|
|
113
|
-
if "DISPLAY" in os.environ and os.environ["DISPLAY"] != "":
|
|
114
|
-
return True # X11 display available
|
|
115
|
-
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
def _exchange_oauth_token(self, code: str, scopes: list[str], oauth_state: "KaggleOAuth.OAuthState"):
|
|
119
|
-
request = ExchangeOAuthTokenRequest()
|
|
120
|
-
request.code = code
|
|
121
|
-
request.code_verifier = oauth_state.code_verifier
|
|
122
|
-
request.grant_type = "authorization_code"
|
|
123
|
-
|
|
124
|
-
response = self._client.security.oauth_client.exchange_oauth_token(request)
|
|
125
|
-
self._creds = KaggleCredentials(
|
|
126
|
-
client=self._client,
|
|
127
|
-
refresh_token=response.refreshToken,
|
|
128
|
-
access_token=response.accessToken,
|
|
129
|
-
access_token_expiration=datetime.now(timezone.utc) + timedelta(seconds=response.expires_in),
|
|
130
|
-
username=response.username,
|
|
131
|
-
scopes=scopes,
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
def _run_oauth_flow(self, scopes: list[str], no_launch_browser: bool) -> KaggleCredentials:
|
|
135
|
-
use_browser = not no_launch_browser and KaggleOAuth._can_open_browser()
|
|
136
|
-
redirect_uri = self._http_client.get_oauth_default_redirect_url()
|
|
137
|
-
if use_browser:
|
|
138
|
-
port = random.randint(8000, 9000)
|
|
139
|
-
redirect_uri = f"http://localhost:{port}"
|
|
140
|
-
self._logger.debug(f"Will listen for the callback at: {redirect_uri}")
|
|
141
|
-
|
|
142
|
-
oauth_state = KaggleOAuth.OAuthState()
|
|
143
|
-
oauth_start_url = self._http_client.build_start_oauth_url(
|
|
144
|
-
client_id=KaggleOAuth.OAUTH_CLIENT_ID,
|
|
145
|
-
redirect_uri=redirect_uri,
|
|
146
|
-
scope=scopes,
|
|
147
|
-
state=oauth_state.state,
|
|
148
|
-
code_challenge=oauth_state.code_challenge,
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
if use_browser:
|
|
152
|
-
webbrowser.open(oauth_start_url)
|
|
153
|
-
print("Your browser has been opened to visit:")
|
|
154
|
-
print(f" {oauth_start_url}\n\n")
|
|
155
|
-
|
|
156
|
-
def exchange_oauth_token(code: str):
|
|
157
|
-
self._exchange_oauth_token(code, scopes, oauth_state)
|
|
158
|
-
|
|
159
|
-
def handler_factory(*args, **kwargs):
|
|
160
|
-
return KaggleOAuth.OAuthCallbackHandler(
|
|
161
|
-
*args,
|
|
162
|
-
oauth=self,
|
|
163
|
-
oauth_state=oauth_state,
|
|
164
|
-
on_success=exchange_oauth_token,
|
|
165
|
-
logger=self._logger,
|
|
166
|
-
**kwargs,
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
self._server_running = True
|
|
170
|
-
with socketserver.TCPServer(("127.0.0.1", port), handler_factory) as httpd:
|
|
171
|
-
self._logger.debug(f"Listening for callback on port {port}...")
|
|
172
|
-
while self._server_running:
|
|
173
|
-
httpd.handle_request()
|
|
174
|
-
self._logger.debug("OAuth flow completed (or server stopped).")
|
|
175
|
-
else:
|
|
176
|
-
print("\nGo to the following link in your browser, and complete the sign-in prompts at Kaggle:\n")
|
|
177
|
-
print(f" {oauth_start_url}")
|
|
178
|
-
print(
|
|
179
|
-
"\nOnce finished, enter the verification code provided in your browser: ",
|
|
180
|
-
end="",
|
|
181
|
-
)
|
|
182
|
-
code = input()
|
|
183
|
-
self._exchange_oauth_token(code, scopes, oauth_state)
|
|
184
|
-
|
|
185
|
-
return self._creds
|
|
186
|
-
|
|
187
|
-
def stop_server(self):
|
|
188
|
-
self._server_running = False
|
|
189
|
-
|
|
190
|
-
def _ensure_creds_valid(self, creds: KaggleCredentials):
|
|
191
|
-
if not creds:
|
|
192
|
-
raise Exception("Authentication failed.")
|
|
193
|
-
return creds.introspect()
|
|
194
|
-
|
|
195
|
-
def authenticate(self, scopes: list[str], no_launch_browser: bool = False) -> KaggleCredentials:
|
|
196
|
-
creds = self._run_oauth_flow(scopes, no_launch_browser)
|
|
197
|
-
username = self._ensure_creds_valid(creds)
|
|
198
|
-
creds.save()
|
|
199
|
-
print(f"\nYou are now logged in as [{username}]\n")
|
|
200
|
-
return creds
|