Skip to content

Commit 8fef673

Browse files
authored
Add request and response for code submissions (#704)
From `kapigen`, at least the `kagglesdk` part is. This was supposed to only have generated code but I accidentally included the "real" code, too. There's a separate PR for the new handler, 33938.
1 parent d87db56 commit 8fef673

File tree

14 files changed

+357
-61
lines changed

14 files changed

+357
-61
lines changed

kaggle/api/kaggle_api_extended.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
import urllib3.exceptions as urllib3_exceptions
3535
from requests import RequestException
3636

37-
from kaggle.models.kaggle_models_extended import ResumableUploadResult, File
37+
from kaggle.models.kaggle_models_extended import ResumableUploadResult, File, \
38+
Kernel
3839

3940
from requests.adapters import HTTPAdapter
4041
from slugify import slugify
@@ -61,7 +62,8 @@
6162
SettingsLicense, DatasetCollaborator
6263
from kagglesdk.kernels.types.kernels_api_service import ApiListKernelsRequest, \
6364
ApiListKernelFilesRequest, ApiSaveKernelRequest, ApiGetKernelRequest, \
64-
ApiListKernelSessionOutputRequest, ApiGetKernelSessionStatusRequest
65+
ApiListKernelSessionOutputRequest, ApiGetKernelSessionStatusRequest, \
66+
ApiSaveKernelResponse
6567
from kagglesdk.kernels.types.kernels_enums import KernelsListSortType, \
6668
KernelsListViewType
6769
from kagglesdk.models.types.model_api_service import ApiListModelsRequest, \
@@ -290,7 +292,7 @@ class KaggleApi:
290292

291293
args = {} # DEBUG Add --local to use localhost
292294
if os.environ.get('KAGGLE_API_ENVIRONMENT') == 'LOCALHOST':
293-
args = {'--local'}
295+
args = {'--verbose','--local'}
294296

295297
# Kernels valid types
296298
valid_push_kernel_types = ['script', 'notebook']
@@ -792,6 +794,39 @@ def competitions_list_cli(self,
792794
else:
793795
print('No competitions found')
794796

797+
def competition_submit_code(self, file_name, message, competition, kernel_slug=None, kernel_version=None, quiet=False):
798+
""" Submit a competition.
799+
800+
Parameters
801+
==========
802+
file_name: the name of the output file created by the kernel
803+
message: the submission description
804+
competition: the competition name; if not given use the 'competition' config value
805+
kernel_slug: the <owner>/<notebook> of the notebook to use for a code competition
806+
kernel_version: the version number, returned by 'kaggle kernels push ...'
807+
quiet: suppress verbose output (default is False)
808+
"""
809+
if competition is None:
810+
competition = self.get_config_value(self.CONFIG_NAME_COMPETITION)
811+
if competition is not None and not quiet:
812+
print('Using competition: ' + competition)
813+
814+
if competition is None:
815+
raise ValueError('No competition specified')
816+
else:
817+
if kernel_version is None:
818+
raise ValueError('Kernel version must be specified')
819+
with self.build_kaggle_client() as kaggle:
820+
submit_request = ApiCreateCodeSubmissionRequest()
821+
submit_request.file_name = file_name
822+
submit_request.competition_name = competition
823+
submit_request.kernel_slug = kernel_slug
824+
submit_request.kernel_version = kernel_version
825+
submit_request.submission_description = message
826+
submit_response = kaggle.competitions.competition_api_client.create_code_submission(
827+
submit_request)
828+
return submit_response
829+
795830
def competition_submit(self, file_name, message, competition, quiet=False):
796831
""" Submit a competition.
797832
@@ -837,6 +872,8 @@ def competition_submit_cli(self,
837872
file_name,
838873
message,
839874
competition,
875+
kernel=None,
876+
version=None,
840877
competition_opt=None,
841878
quiet=False):
842879
""" Submit a competition using the client. Arguments are same as for
@@ -847,12 +884,20 @@ def competition_submit_cli(self,
847884
file_name: the competition metadata file
848885
message: the submission description
849886
competition: the competition name; if not given use the 'competition' config value
887+
kernel: the name of the kernel to submit to a code competition
888+
version: the version of the kernel to submit to a code competition, e.g. '1'
850889
quiet: suppress verbose output (default is False)
851890
competition_opt: an alternative competition option provided by cli
852891
"""
892+
if kernel and not version or version and not kernel:
893+
raise ValueError('Code competition submissions require both the output file name and the version label')
853894
competition = competition or competition_opt
854895
try:
855-
submit_result = self.competition_submit(file_name, message, competition,
896+
if kernel:
897+
submit_result = self.competition_submit_code(file_name, message, competition,
898+
kernel, version, quiet)
899+
else:
900+
submit_result = self.competition_submit(file_name, message, competition,
856901
quiet)
857902
except RequestException as e:
858903
if e.response and e.response.status_code == 404:
@@ -2369,7 +2414,7 @@ def kernels_initialize_cli(self, folder=None):
23692414
meta_file = self.kernels_initialize(folder)
23702415
print('Kernel metadata template written to: ' + meta_file)
23712416

2372-
def kernels_push(self, folder, timeout=None):
2417+
def kernels_push(self, folder, timeout=None) -> ApiSaveKernelResponse:
23732418
""" Read the metadata file and kernel files from a notebook, validate
23742419
both, and use the Kernel API to push to Kaggle if all is valid.
23752420
Parameters

kaggle/cli.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,17 @@ def parse_competitions(subparsers):
245245
required=False,
246246
help=argparse.SUPPRESS)
247247
parser_competitions_submit_required.add_argument(
248-
'-f', '--file', dest='file_name', required=True, help=Help.param_upfile)
248+
'-f', '--file', dest='file_name', help=Help.param_upfile)
249+
parser_competitions_submit_optional.add_argument(
250+
'-k', '--kernel', dest='kernel', help=Help.param_code_kernel)
249251
parser_competitions_submit_required.add_argument(
250252
'-m',
251253
'--message',
252254
dest='message',
253255
required=True,
254256
help=Help.param_competition_message)
257+
parser_competitions_submit_optional.add_argument(
258+
'-v', '--version', dest='version', help=Help.param_code_version)
255259
parser_competitions_submit_optional.add_argument(
256260
'-q', '--quiet', dest='quiet', action='store_true', help=Help.param_quiet)
257261
parser_competitions_submit._action_groups.append(
@@ -1420,7 +1424,9 @@ class Help(object):
14201424
param_delete_old_version = 'Delete old versions of this dataset'
14211425
param_force = ('Skip check whether local version of file is up to date, force'
14221426
' file download')
1423-
param_upfile = 'File for upload (full path)'
1427+
param_upfile = 'File for upload (full path), or the name of the output file produced by a kernel (for code competitions)'
1428+
param_code_kernel = 'Name of kernel (notebook) to submit to a code competition'
1429+
param_code_version = 'Version of kernel to submit to a code competition, e.g. "Version 1"'
14241430
param_csv = 'Print results in CSV format (if not set print in table format)'
14251431
param_page = 'Page number for results paging. Page size is 20 by default'
14261432
# NOTE: Default and max page size are set by the mid-tier code.

kagglesdk/competitions/services/competition_api_service.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from kagglesdk.common.types.file_download import FileDownload
22
from kagglesdk.common.types.http_redirect import HttpRedirect
3-
from kagglesdk.competitions.types.competition_api_service import ApiCreateSubmissionRequest, ApiCreateSubmissionResponse, ApiDownloadDataFileRequest, ApiDownloadDataFilesRequest, ApiDownloadLeaderboardRequest, ApiGetLeaderboardRequest, ApiGetLeaderboardResponse, ApiGetSubmissionRequest, ApiListCompetitionsRequest, ApiListCompetitionsResponse, ApiListDataFilesRequest, ApiListDataFilesResponse, ApiListSubmissionsRequest, ApiListSubmissionsResponse, ApiStartSubmissionUploadRequest, ApiStartSubmissionUploadResponse, ApiSubmission
3+
from kagglesdk.competitions.types.competition_api_service import ApiCreateCodeSubmissionRequest, ApiCreateSubmissionRequest, ApiCreateSubmissionResponse, ApiDownloadDataFileRequest, ApiDownloadDataFilesRequest, ApiDownloadLeaderboardRequest, ApiGetLeaderboardRequest, ApiGetLeaderboardResponse, ApiGetSubmissionRequest, ApiListCompetitionsRequest, ApiListCompetitionsResponse, ApiListDataFilesRequest, ApiListDataFilesResponse, ApiListSubmissionsRequest, ApiListSubmissionsResponse, ApiStartSubmissionUploadRequest, ApiStartSubmissionUploadResponse, ApiSubmission
44
from kagglesdk.kaggle_http_client import KaggleHttpClient
55

66
class CompetitionApiClient(object):
@@ -80,6 +80,18 @@ def create_submission(self, request: ApiCreateSubmissionRequest = None) -> ApiCr
8080

8181
return self._client.call("competitions.CompetitionApiService", "ApiCreateSubmission", request, ApiCreateSubmissionResponse)
8282

83+
def create_code_submission(self, request: ApiCreateCodeSubmissionRequest = None) -> ApiCreateSubmissionResponse:
84+
r"""
85+
Args:
86+
request (ApiCreateCodeSubmissionRequest):
87+
The request object; initialized to empty instance if not specified.
88+
"""
89+
90+
if request is None:
91+
request = ApiCreateCodeSubmissionRequest()
92+
93+
return self._client.call("competitions.CompetitionApiService", "ApiCreateCodeSubmission", request, ApiCreateSubmissionResponse)
94+
8395
def get_submission(self, request: ApiGetSubmissionRequest = None) -> ApiSubmission:
8496
r"""
8597
Args:

kagglesdk/competitions/types/competition_api_service.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,99 @@
44
from kagglesdk.kaggle_object import *
55
from typing import Optional, List
66

7+
class ApiCreateCodeSubmissionRequest(KaggleObject):
8+
r"""
9+
Attributes:
10+
competition_name (str)
11+
kernel_slug (str)
12+
kernel_version (str)
13+
file_name (str)
14+
submission_description (str)
15+
"""
16+
17+
def __init__(self):
18+
self._competition_name = ""
19+
self._kernel_slug = ""
20+
self._kernel_version = ""
21+
self._file_name = ""
22+
self._submission_description = None
23+
self._freeze()
24+
25+
@property
26+
def competition_name(self) -> str:
27+
return self._competition_name
28+
29+
@competition_name.setter
30+
def competition_name(self, competition_name: str):
31+
if competition_name is None:
32+
del self.competition_name
33+
return
34+
if not isinstance(competition_name, str):
35+
raise TypeError('competition_name must be of type str')
36+
self._competition_name = competition_name
37+
38+
@property
39+
def kernel_slug(self) -> str:
40+
return self._kernel_slug
41+
42+
@kernel_slug.setter
43+
def kernel_slug(self, kernel_slug: str):
44+
if kernel_slug is None:
45+
del self.kernel_slug
46+
return
47+
if not isinstance(kernel_slug, str):
48+
raise TypeError('kernel_slug must be of type str')
49+
self._kernel_slug = kernel_slug
50+
51+
@property
52+
def kernel_version(self) -> str:
53+
return self._kernel_version
54+
55+
@kernel_version.setter
56+
def kernel_version(self, kernel_version: str):
57+
if kernel_version is None:
58+
del self.kernel_version
59+
return
60+
if not isinstance(kernel_version, str):
61+
raise TypeError('kernel_version must be of type str')
62+
self._kernel_version = kernel_version
63+
64+
@property
65+
def file_name(self) -> str:
66+
return self._file_name
67+
68+
@file_name.setter
69+
def file_name(self, file_name: str):
70+
if file_name is None:
71+
del self.file_name
72+
return
73+
if not isinstance(file_name, str):
74+
raise TypeError('file_name must be of type str')
75+
self._file_name = file_name
76+
77+
@property
78+
def submission_description(self) -> str:
79+
return self._submission_description or ""
80+
81+
@submission_description.setter
82+
def submission_description(self, submission_description: str):
83+
if submission_description is None:
84+
del self.submission_description
85+
return
86+
if not isinstance(submission_description, str):
87+
raise TypeError('submission_description must be of type str')
88+
self._submission_description = submission_description
89+
90+
def endpoint(self):
91+
path = '/api/v1/competitions/submissions/submit-notebook/{competition_name}'
92+
return path.format_map(self.to_field_map(self))
93+
94+
95+
@staticmethod
96+
def method():
97+
return 'POST'
98+
99+
7100
class ApiCreateSubmissionRequest(KaggleObject):
8101
r"""
9102
Attributes:
@@ -1719,6 +1812,14 @@ def total_count(self, total_count: int):
17191812
self._total_count = total_count
17201813

17211814

1815+
ApiCreateCodeSubmissionRequest._fields = [
1816+
FieldMetadata("competitionName", "competition_name", "_competition_name", str, "", PredefinedSerializer()),
1817+
FieldMetadata("kernelSlug", "kernel_slug", "_kernel_slug", str, "", PredefinedSerializer()),
1818+
FieldMetadata("kernelVersion", "kernel_version", "_kernel_version", str, "", PredefinedSerializer()),
1819+
FieldMetadata("fileName", "file_name", "_file_name", str, "", PredefinedSerializer()),
1820+
FieldMetadata("submissionDescription", "submission_description", "_submission_description", str, None, PredefinedSerializer(), optional=True),
1821+
]
1822+
17221823
ApiCreateSubmissionRequest._fields = [
17231824
FieldMetadata("competitionName", "competition_name", "_competition_name", str, "", PredefinedSerializer()),
17241825
FieldMetadata("blobFileTokens", "blob_file_tokens", "_blob_file_tokens", str, "", PredefinedSerializer()),

kagglesdk/kaggle_http_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# currently usable by the CLI.
1919

2020
# TODO: Extend kapigen to add a boolean to these requests indicating that they use forms.
21-
REQUESTS_REQUIRING_FORMS = ['ApiUploadDatasetFileRequest', 'ApiCreateSubmissionRequest', 'ApiStartSubmissionUploadRequest', 'ApiUploadModelFileRequest']
21+
REQUESTS_REQUIRING_FORMS = ['ApiUploadDatasetFileRequest', 'ApiCreateSubmissionRequest', 'ApiCreateCodeSubmissionRequest', 'ApiStartSubmissionUploadRequest', 'ApiUploadModelFileRequest']
2222

2323
def _headers_to_str(headers):
2424
return '\n'.join(f'{k}: {v}' for k, v in headers.items())

kagglesdk/models/types/model_api_service.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,7 +1169,6 @@ class ApiListModelGatingUserConsentsRequest(KaggleObject):
11691169
model_slug (str)
11701170
review_status (GatingAgreementRequestsReviewStatus)
11711171
filters: a null value means the filter is off.
1172-
expiry_status (GatingAgreementRequestsExpiryStatus)
11731172
is_user_request_data_expired (bool)
11741173
page_size (int)
11751174
paging
@@ -1180,7 +1179,6 @@ def __init__(self):
11801179
self._owner_slug = ""
11811180
self._model_slug = ""
11821181
self._review_status = None
1183-
self._expiry_status = None
11841182
self._is_user_request_data_expired = None
11851183
self._page_size = None
11861184
self._page_token = None
@@ -1226,19 +1224,6 @@ def review_status(self, review_status: 'GatingAgreementRequestsReviewStatus'):
12261224
raise TypeError('review_status must be of type GatingAgreementRequestsReviewStatus')
12271225
self._review_status = review_status
12281226

1229-
@property
1230-
def expiry_status(self) -> 'GatingAgreementRequestsExpiryStatus':
1231-
return self._expiry_status or GatingAgreementRequestsExpiryStatus.GATING_AGREEMENT_REQUESTS_EXPIRY_STATUS_UNSPECIFIED
1232-
1233-
@expiry_status.setter
1234-
def expiry_status(self, expiry_status: 'GatingAgreementRequestsExpiryStatus'):
1235-
if expiry_status is None:
1236-
del self.expiry_status
1237-
return
1238-
if not isinstance(expiry_status, GatingAgreementRequestsExpiryStatus):
1239-
raise TypeError('expiry_status must be of type GatingAgreementRequestsExpiryStatus')
1240-
self._expiry_status = expiry_status
1241-
12421227
@property
12431228
def is_user_request_data_expired(self) -> bool:
12441229
return self._is_user_request_data_expired or False
@@ -3531,7 +3516,6 @@ def e(self, e: str):
35313516
FieldMetadata("ownerSlug", "owner_slug", "_owner_slug", str, "", PredefinedSerializer()),
35323517
FieldMetadata("modelSlug", "model_slug", "_model_slug", str, "", PredefinedSerializer()),
35333518
FieldMetadata("reviewStatus", "review_status", "_review_status", GatingAgreementRequestsReviewStatus, None, EnumSerializer(), optional=True),
3534-
FieldMetadata("expiryStatus", "expiry_status", "_expiry_status", GatingAgreementRequestsExpiryStatus, None, EnumSerializer(), optional=True),
35353519
FieldMetadata("isUserRequestDataExpired", "is_user_request_data_expired", "_is_user_request_data_expired", bool, None, PredefinedSerializer(), optional=True),
35363520
FieldMetadata("pageSize", "page_size", "_page_size", int, None, PredefinedSerializer(), optional=True),
35373521
FieldMetadata("pageToken", "page_token", "_page_token", str, None, PredefinedSerializer(), optional=True),

kagglesdk/models/types/model_enums.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import enum
22

3-
class GatingAgreementRequestsExpiryStatus(enum.Enum):
4-
GATING_AGREEMENT_REQUESTS_EXPIRY_STATUS_UNSPECIFIED = 0
5-
GATING_AGREEMENT_REQUESTS_EXPIRY_STATUS_NOT_EXPIRED = 1
6-
GATING_AGREEMENT_REQUESTS_EXPIRY_STATUS_IS_EXPIRED = 2
7-
83
class GatingAgreementRequestsReviewStatus(enum.Enum):
94
GATING_AGREEMENT_REQUESTS_REVIEW_STATUS_UNSPECIFIED = 0
105
GATING_AGREEMENT_REQUESTS_REVIEW_STATUS_PENDING = 1
@@ -58,3 +53,8 @@ class ModelVersionLinkType(enum.Enum):
5853
MODEL_VERSION_LINK_TYPE_VERTEX_OPEN = 1
5954
MODEL_VERSION_LINK_TYPE_VERTEX_DEPLOY = 2
6055

56+
class GatingAgreementRequestsExpiryStatus(enum.Enum):
57+
GATING_AGREEMENT_REQUESTS_EXPIRY_STATUS_UNSPECIFIED = 0
58+
GATING_AGREEMENT_REQUESTS_EXPIRY_STATUS_NOT_EXPIRED = 1
59+
GATING_AGREEMENT_REQUESTS_EXPIRY_STATUS_IS_EXPIRED = 2
60+

0 commit comments

Comments
 (0)