Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions kagglesdk/competitions/types/hackathons.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from kagglesdk.competitions.types.competition_enums import HackathonTrackPrizeType
from kagglesdk.competitions.types.team import Team
from kagglesdk.discussions.types.writeup_types import WriteUp
Expand Down Expand Up @@ -204,6 +205,7 @@ class HackathonWriteUp(KaggleObject):
should_create_doi (bool)
Whether or not the user indicated a DOI should be created for the hackathon
writeup when the competition closes.
update_time (datetime)
"""

def __init__(self):
Expand All @@ -217,6 +219,7 @@ def __init__(self):
self._owner_host_user_id = None
self._owner_judge_user_id = None
self._should_create_doi = None
self._update_time = None
self._freeze()

@property
Expand Down Expand Up @@ -357,6 +360,18 @@ def should_create_doi(self, should_create_doi: Optional[bool]):
raise TypeError('should_create_doi must be of type bool')
self._should_create_doi = should_create_doi

@property
def update_time(self) -> datetime:
return self._update_time or None

@update_time.setter
def update_time(self, update_time: Optional[datetime]):
if update_time is None:
del self.update_time
return
if not isinstance(update_time, datetime):
raise TypeError('update_time must be of type datetime')
self._update_time = update_time

HackathonTrack._fields = [
FieldMetadata("id", "id", "_id", int, 0, PredefinedSerializer()),
Expand Down Expand Up @@ -386,5 +401,6 @@ def should_create_doi(self, should_create_doi: Optional[bool]):
FieldMetadata("ownerHostUserId", "owner_host_user_id", "_owner_host_user_id", int, None, PredefinedSerializer(), optional=True),
FieldMetadata("ownerJudgeUserId", "owner_judge_user_id", "_owner_judge_user_id", int, None, PredefinedSerializer(), optional=True),
FieldMetadata("shouldCreateDoi", "should_create_doi", "_should_create_doi", bool, None, PredefinedSerializer(), optional=True),
FieldMetadata("updateTime", "update_time", "_update_time", datetime, None, DateTimeSerializer(), optional=True),
]

88 changes: 55 additions & 33 deletions kagglesdk/kaggle_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ def _get_apikey_creds():
if not kaggle_json or not kaggle_json.strip():
return None

api_key_data = json.loads(kaggle_json)
username = api_key_data["username"]
api_key = api_key_data["key"]
return username, api_key
try:
# Be careful, since the file may be used for more than credential storage.
api_key_data = json.loads(kaggle_json)
username = api_key_data["username"]
api_key = api_key_data["key"]
return username, api_key
except KeyError:
return None


class KaggleHttpClient(object):
Expand All @@ -55,14 +59,14 @@ class KaggleHttpClient(object):
_xsrf_header_name = "X-XSRF-TOKEN"

def __init__(
self,
env: KaggleEnv = None,
verbose: bool = False,
username: str = None,
password: str = None,
api_token: str = None,
user_agent: str = "kaggle-api/v1.7.0", # Was: V2
response_processor=None,
self,
env: KaggleEnv = None,
verbose: bool = False,
username: str = None,
password: str = None,
api_token: str = None,
user_agent: str = "kaggle-api/v1.7.0", # Was: V2
response_processor=None,
):
self._env = env or get_env()
self._signed_in = None
Expand All @@ -76,29 +80,35 @@ def __init__(
self._response_processor = response_processor

def call(
self,
service_name: str,
request_name: str,
request: KaggleObject,
response_type: Type[KaggleObject],
self,
service_name: str,
request_name: str,
request: KaggleObject,
response_type: Type[KaggleObject],
):
self._init_session()
http_request = self._prepare_request(service_name, request_name, request)

# Merge environment settings into session
settings = self._session.merge_environment_settings(http_request.url, {}, None, None, None)
settings = self._session.merge_environment_settings(
http_request.url, {}, None, None, None
)

# Use stream=True for file downloads to avoid loading entire file into memory
# See: https://github.com/Kaggle/kaggle-api/issues/754
if response_type is not None and (response_type == FileDownload or response_type == HttpRedirect):
if response_type is not None and (
response_type == FileDownload or response_type == HttpRedirect
):
settings["stream"] = True

http_response = self._session.send(http_request, **settings)

response = self._prepare_response(response_type, http_response)
return response

def _prepare_request(self, service_name: str, request_name: str, request: KaggleObject):
def _prepare_request(
self, service_name: str, request_name: str, request: KaggleObject
):
request_url = self._get_request_url(service_name, request_name)
http_request = requests.Request(
method="POST",
Expand All @@ -118,7 +128,9 @@ def _prepare_response(self, response_type, http_response):
if "application/json" in http_response.headers["Content-Type"]:
resp = http_response.json()
if "code" in resp and resp["code"] >= 400:
raise requests.exceptions.HTTPError(resp["message"], response=http_response)
raise requests.exceptions.HTTPError(
resp["message"], response=http_response
)
except KeyError:
pass
http_response.raise_for_status()
Expand All @@ -133,7 +145,9 @@ def _print_request(self, request):
if not self._verbose:
return
self._print("---------------------Request----------------------")
self._print(f"{request.method} {request.url}\n{_headers_to_str(request.headers)}\n\n{request.body}")
self._print(
f"{request.method} {request.url}\n{_headers_to_str(request.headers)}\n\n{request.body}"
)
self._print("--------------------------------------------------")

def _print_response(self, response, body=True):
Expand Down Expand Up @@ -162,7 +176,9 @@ def _init_session(self):
return self._session

self._session = requests.Session()
self._session.headers.update({"User-Agent": self._user_agent, "Content-Type": "application/json"})
self._session.headers.update(
{"User-Agent": self._user_agent, "Content-Type": "application/json"}
)

iap_token = self._get_iap_token_if_required()
if iap_token is not None:
Expand Down Expand Up @@ -203,17 +219,19 @@ def _fill_xsrf_token(self, iap_token):

self._session.headers.update(
{
KaggleHttpClient._xsrf_header_name: self._session.cookies[KaggleHttpClient._xsrf_cookie_name],
KaggleHttpClient._xsrf_header_name: self._session.cookies[
KaggleHttpClient._xsrf_cookie_name
],
}
)

def build_start_oauth_url(
self,
client_id: str,
redirect_uri: str,
scope: list[str],
state: str,
code_challenge: str,
self,
client_id: str,
redirect_uri: str,
scope: list[str],
state: str,
code_challenge: str,
) -> str:
params = {
"response_type": "code",
Expand All @@ -234,7 +252,9 @@ def get_oauth_default_redirect_url(self) -> str:
return f"{self.get_non_api_endpoint()}/account/api/oauth/token"

def get_non_api_endpoint(self) -> str:
return "https://www.kaggle.com" if self._env == KaggleEnv.PROD else self._endpoint
return (
"https://www.kaggle.com" if self._env == KaggleEnv.PROD else self._endpoint
)

class BearerAuth(requests.auth.AuthBase):

Expand All @@ -250,7 +270,7 @@ def _try_fill_auth(self):
return

if self._api_token is None:
(api_token, _) = get_access_token_from_env()
api_token, _ = get_access_token_from_env()
self._api_token = api_token

if self._api_token is not None:
Expand All @@ -272,5 +292,7 @@ def _try_fill_auth(self):
def _get_request_url(self, service_name: str, request_name: str):
# On prod, API endpoints are served under https://api.kaggle.com/v1,
# but on staging/admin/local, they are served under http://localhost/api/v1.
base_url = self._endpoint if self._env == KaggleEnv.PROD else f"{self._endpoint}/api"
base_url = (
self._endpoint if self._env == KaggleEnv.PROD else f"{self._endpoint}/api"
)
return f"{base_url}/v1/{service_name}/{request_name}"
Loading