kaggle 1.8.2__py3-none-any.whl → 1.8.3__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 (128) hide show
  1. kaggle/__init__.py +1 -1
  2. kaggle/api/kaggle_api_extended.py +24 -2
  3. {kaggle-1.8.2.dist-info → kaggle-1.8.3.dist-info}/METADATA +3 -3
  4. kaggle-1.8.3.dist-info/RECORD +28 -0
  5. {kaggle-1.8.2.dist-info → kaggle-1.8.3.dist-info}/WHEEL +1 -1
  6. kaggle-1.8.2.dist-info/RECORD +0 -148
  7. kagglesdk/LICENSE +0 -201
  8. kagglesdk/__init__.py +0 -6
  9. kagglesdk/admin/__init__.py +0 -0
  10. kagglesdk/admin/services/__init__.py +0 -0
  11. kagglesdk/admin/services/inbox_file_service.py +0 -22
  12. kagglesdk/admin/types/__init__.py +0 -0
  13. kagglesdk/admin/types/inbox_file_service.py +0 -74
  14. kagglesdk/benchmarks/__init__.py +0 -0
  15. kagglesdk/benchmarks/services/__init__.py +0 -0
  16. kagglesdk/benchmarks/services/benchmarks_api_service.py +0 -19
  17. kagglesdk/benchmarks/types/__init__.py +0 -0
  18. kagglesdk/benchmarks/types/benchmark_types.py +0 -307
  19. kagglesdk/benchmarks/types/benchmarks_api_service.py +0 -243
  20. kagglesdk/blobs/__init__.py +0 -0
  21. kagglesdk/blobs/services/__init__.py +0 -0
  22. kagglesdk/blobs/services/blob_api_service.py +0 -25
  23. kagglesdk/blobs/types/__init__.py +0 -0
  24. kagglesdk/blobs/types/blob_api_service.py +0 -177
  25. kagglesdk/common/__init__.py +0 -0
  26. kagglesdk/common/services/__init__.py +0 -0
  27. kagglesdk/common/services/operations_service.py +0 -46
  28. kagglesdk/common/types/__init__.py +0 -0
  29. kagglesdk/common/types/file_download.py +0 -102
  30. kagglesdk/common/types/http_redirect.py +0 -105
  31. kagglesdk/common/types/operations.py +0 -194
  32. kagglesdk/common/types/operations_service.py +0 -48
  33. kagglesdk/community/__init__.py +0 -0
  34. kagglesdk/community/types/__init__.py +0 -0
  35. kagglesdk/community/types/content_enums.py +0 -44
  36. kagglesdk/community/types/organization.py +0 -410
  37. kagglesdk/competitions/__init__.py +0 -0
  38. kagglesdk/competitions/services/__init__.py +0 -0
  39. kagglesdk/competitions/services/competition_api_service.py +0 -178
  40. kagglesdk/competitions/types/__init__.py +0 -0
  41. kagglesdk/competitions/types/competition.py +0 -14
  42. kagglesdk/competitions/types/competition_api_service.py +0 -2393
  43. kagglesdk/competitions/types/competition_enums.py +0 -53
  44. kagglesdk/competitions/types/search_competitions.py +0 -28
  45. kagglesdk/competitions/types/submission_status.py +0 -9
  46. kagglesdk/datasets/__init__.py +0 -0
  47. kagglesdk/datasets/databundles/__init__.py +0 -0
  48. kagglesdk/datasets/databundles/types/__init__.py +0 -0
  49. kagglesdk/datasets/databundles/types/databundle_api_types.py +0 -540
  50. kagglesdk/datasets/services/__init__.py +0 -0
  51. kagglesdk/datasets/services/dataset_api_service.py +0 -195
  52. kagglesdk/datasets/types/__init__.py +0 -0
  53. kagglesdk/datasets/types/dataset_api_service.py +0 -3047
  54. kagglesdk/datasets/types/dataset_enums.py +0 -103
  55. kagglesdk/datasets/types/dataset_service.py +0 -145
  56. kagglesdk/datasets/types/dataset_types.py +0 -646
  57. kagglesdk/datasets/types/search_datasets.py +0 -6
  58. kagglesdk/discussions/__init__.py +0 -0
  59. kagglesdk/discussions/types/__init__.py +0 -0
  60. kagglesdk/discussions/types/search_discussions.py +0 -43
  61. kagglesdk/discussions/types/writeup_enums.py +0 -11
  62. kagglesdk/education/__init__.py +0 -0
  63. kagglesdk/education/services/__init__.py +0 -0
  64. kagglesdk/education/services/education_api_service.py +0 -19
  65. kagglesdk/education/types/__init__.py +0 -0
  66. kagglesdk/education/types/education_api_service.py +0 -248
  67. kagglesdk/education/types/education_service.py +0 -139
  68. kagglesdk/kaggle_client.py +0 -101
  69. kagglesdk/kaggle_creds.py +0 -148
  70. kagglesdk/kaggle_env.py +0 -104
  71. kagglesdk/kaggle_http_client.py +0 -269
  72. kagglesdk/kaggle_oauth.py +0 -200
  73. kagglesdk/kaggle_object.py +0 -344
  74. kagglesdk/kernels/__init__.py +0 -0
  75. kagglesdk/kernels/services/__init__.py +0 -0
  76. kagglesdk/kernels/services/kernels_api_service.py +0 -146
  77. kagglesdk/kernels/types/__init__.py +0 -0
  78. kagglesdk/kernels/types/kernels_api_service.py +0 -2451
  79. kagglesdk/kernels/types/kernels_enums.py +0 -39
  80. kagglesdk/kernels/types/search_kernels.py +0 -6
  81. kagglesdk/licenses/__init__.py +0 -0
  82. kagglesdk/licenses/types/__init__.py +0 -0
  83. kagglesdk/licenses/types/licenses_types.py +0 -182
  84. kagglesdk/models/__init__.py +0 -0
  85. kagglesdk/models/services/__init__.py +0 -0
  86. kagglesdk/models/services/model_api_service.py +0 -280
  87. kagglesdk/models/services/model_service.py +0 -19
  88. kagglesdk/models/types/__init__.py +0 -0
  89. kagglesdk/models/types/model_api_service.py +0 -4069
  90. kagglesdk/models/types/model_enums.py +0 -68
  91. kagglesdk/models/types/model_service.py +0 -275
  92. kagglesdk/models/types/model_types.py +0 -1338
  93. kagglesdk/models/types/search_models.py +0 -8
  94. kagglesdk/search/__init__.py +0 -0
  95. kagglesdk/search/services/__init__.py +0 -0
  96. kagglesdk/search/services/search_api_service.py +0 -19
  97. kagglesdk/search/types/__init__.py +0 -0
  98. kagglesdk/search/types/search_api_service.py +0 -2435
  99. kagglesdk/search/types/search_content_shared.py +0 -50
  100. kagglesdk/search/types/search_enums.py +0 -45
  101. kagglesdk/search/types/search_service.py +0 -303
  102. kagglesdk/security/__init__.py +0 -0
  103. kagglesdk/security/services/__init__.py +0 -0
  104. kagglesdk/security/services/iam_service.py +0 -31
  105. kagglesdk/security/services/oauth_service.py +0 -58
  106. kagglesdk/security/types/__init__.py +0 -0
  107. kagglesdk/security/types/authentication.py +0 -171
  108. kagglesdk/security/types/iam_service.py +0 -496
  109. kagglesdk/security/types/oauth_service.py +0 -1181
  110. kagglesdk/security/types/roles.py +0 -8
  111. kagglesdk/security/types/security_types.py +0 -159
  112. kagglesdk/test/__init__.py +0 -0
  113. kagglesdk/test/test_client.py +0 -41
  114. kagglesdk/users/__init__.py +0 -0
  115. kagglesdk/users/services/__init__.py +0 -0
  116. kagglesdk/users/services/account_service.py +0 -31
  117. kagglesdk/users/services/group_api_service.py +0 -31
  118. kagglesdk/users/types/__init__.py +0 -0
  119. kagglesdk/users/types/account_service.py +0 -345
  120. kagglesdk/users/types/group_api_service.py +0 -315
  121. kagglesdk/users/types/group_types.py +0 -165
  122. kagglesdk/users/types/groups_enum.py +0 -8
  123. kagglesdk/users/types/progression_service.py +0 -9
  124. kagglesdk/users/types/search_users.py +0 -23
  125. kagglesdk/users/types/user_avatar.py +0 -226
  126. kagglesdk/users/types/users_enums.py +0 -22
  127. {kaggle-1.8.2.dist-info → kaggle-1.8.3.dist-info}/entry_points.txt +0 -0
  128. {kaggle-1.8.2.dist-info → kaggle-1.8.3.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)
@@ -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