From b67e00ff2d1bc4a72cc047317c5a116770ef39e6 Mon Sep 17 00:00:00 2001 From: Erdal Sivri Date: Wed, 5 Nov 2025 17:19:52 +0000 Subject: [PATCH] Update kagglesdk to the latest --- .../competitions/types/search_competitions.py | 1 + .../discussions/types/search_discussions.py | 1 + src/kagglesdk/kaggle_http_client.py | 5 +- .../security/services/oauth_service.py | 16 +- src/kagglesdk/security/types/oauth_service.py | 582 +++++++++++++++++- .../users/services/group_api_service.py | 14 +- .../users/types/group_api_service.py | 232 +++++-- 7 files changed, 792 insertions(+), 59 deletions(-) diff --git a/src/kagglesdk/competitions/types/search_competitions.py b/src/kagglesdk/competitions/types/search_competitions.py index cd548102..36a980fa 100644 --- a/src/kagglesdk/competitions/types/search_competitions.py +++ b/src/kagglesdk/competitions/types/search_competitions.py @@ -18,6 +18,7 @@ class SearchCompetitionsRole(enum.Enum): SEARCH_COMPETITIONS_ROLE_PARTICIPANT = 2 SEARCH_COMPETITIONS_ROLE_PARTICIPANT_ONLY = 3 """Excludes competitions user hosted, even if they are also a participant""" + SEARCH_COMPETITIONS_ROLE_JUDGE = 4 class SearchCompetitionsStatus(enum.Enum): SEARCH_COMPETITIONS_STATUS_ANY = 0 diff --git a/src/kagglesdk/discussions/types/search_discussions.py b/src/kagglesdk/discussions/types/search_discussions.py index 68a5fe20..5416ba78 100644 --- a/src/kagglesdk/discussions/types/search_discussions.py +++ b/src/kagglesdk/discussions/types/search_discussions.py @@ -19,6 +19,7 @@ class SearchDiscussionsSourceType(enum.Enum): SEARCH_DISCUSSIONS_SOURCE_TYPE_COMPETITION_SOLUTION = 6 SEARCH_DISCUSSIONS_SOURCE_TYPE_MODEL = 7 SEARCH_DISCUSSIONS_SOURCE_TYPE_WRITE_UP = 8 + SEARCH_DISCUSSIONS_SOURCE_TYPE_LEARN_TRACK = 9 class SearchDiscussionsTopicType(enum.Enum): SEARCH_DISCUSSIONS_TOPIC_TYPE_UNSPECIFIED = 0 diff --git a/src/kagglesdk/kaggle_http_client.py b/src/kagglesdk/kaggle_http_client.py index 67d12471..eff42483 100644 --- a/src/kagglesdk/kaggle_http_client.py +++ b/src/kagglesdk/kaggle_http_client.py @@ -255,4 +255,7 @@ def _try_fill_auth(self): self._signed_in = False def _get_request_url(self, service_name: str, request_name: str): - return f"{self._endpoint}/v1/{service_name}/{request_name}" + # 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" + return f"{base_url}/v1/{service_name}/{request_name}" diff --git a/src/kagglesdk/security/services/oauth_service.py b/src/kagglesdk/security/services/oauth_service.py index e7b4043e..26726a84 100644 --- a/src/kagglesdk/security/services/oauth_service.py +++ b/src/kagglesdk/security/services/oauth_service.py @@ -1,6 +1,6 @@ from kagglesdk.common.types.http_redirect import HttpRedirect from kagglesdk.kaggle_http_client import KaggleHttpClient -from kagglesdk.security.types.oauth_service import ExchangeOAuthTokenRequest, ExchangeOAuthTokenResponse, IntrospectTokenRequest, IntrospectTokenResponse, StartOAuthFlowRequest +from kagglesdk.security.types.oauth_service import ExchangeOAuthTokenRequest, ExchangeOAuthTokenResponse, IntrospectTokenRequest, IntrospectTokenResponse, RegisterOAuthClientRequest, RegisterOAuthClientResponse, StartOAuthFlowRequest class OAuthClient(object): @@ -42,3 +42,17 @@ def introspect_token(self, request: IntrospectTokenRequest = None) -> Introspect request = IntrospectTokenRequest() return self._client.call("security.OAuthService", "IntrospectToken", request, IntrospectTokenResponse) + + def register_oauth_client(self, request: RegisterOAuthClientRequest = None) -> RegisterOAuthClientResponse: + r""" + Dynamic Client Registration Endpoint (RFC 7591) + + Args: + request (RegisterOAuthClientRequest): + The request object; initialized to empty instance if not specified. + """ + + if request is None: + request = RegisterOAuthClientRequest() + + return self._client.call("security.OAuthService", "RegisterOAuthClient", request, RegisterOAuthClientResponse) diff --git a/src/kagglesdk/security/types/oauth_service.py b/src/kagglesdk/security/types/oauth_service.py index 472d7647..578b91c2 100644 --- a/src/kagglesdk/security/types/oauth_service.py +++ b/src/kagglesdk/security/types/oauth_service.py @@ -1,5 +1,5 @@ from kagglesdk.kaggle_object import * -from typing import Optional +from typing import Optional, List class ExchangeOAuthTokenRequest(KaggleObject): r""" @@ -13,13 +13,28 @@ class ExchangeOAuthTokenRequest(KaggleObject): code_verifier (str) Original code_verifier (hash of code_challenge) for PKCE protection. grant_type (str) - Must be set to 'authorization_code'. + Can be 'authorization_code' or 'refresh_token'. + client_id (str) + The client id of the OAuth client that initiated this flow. + redirect_uri (str) + The redirect URI that was used in the initial authorization request. + resource (str) + The 'resource' parameter is not part of the OAuth2 spec, but is sent by + some clients. We are capturing it here to avoid 'invalid field' errors. + refresh_token (str) + This field is used by MCP clients to refresh an access token. The client + sends a refresh_token to the standard /token endpoint, and this field + allows the server to correctly deserialize the request. """ def __init__(self): - self._code = "" + self._code = None self._code_verifier = None self._grant_type = "" + self._client_id = None + self._redirect_uri = None + self._resource = None + self._refresh_token = None self._freeze() @property @@ -31,10 +46,10 @@ def code(self) -> str: initiator as a query string parameter to their redirect_uri (https://dataverse.org?code=808f9afcabb3489a8b30353a8ae4dc4b) """ - return self._code + return self._code or "" @code.setter - def code(self, code: str): + def code(self, code: Optional[str]): if code is None: del self.code return @@ -58,7 +73,7 @@ def code_verifier(self, code_verifier: Optional[str]): @property def grant_type(self) -> str: - """Must be set to 'authorization_code'.""" + """Can be 'authorization_code' or 'refresh_token'.""" return self._grant_type @grant_type.setter @@ -70,6 +85,69 @@ def grant_type(self, grant_type: str): raise TypeError('grant_type must be of type str') self._grant_type = grant_type + @property + def client_id(self) -> str: + """The client id of the OAuth client that initiated this flow.""" + return self._client_id or "" + + @client_id.setter + def client_id(self, client_id: Optional[str]): + if client_id is None: + del self.client_id + return + if not isinstance(client_id, str): + raise TypeError('client_id must be of type str') + self._client_id = client_id + + @property + def redirect_uri(self) -> str: + """The redirect URI that was used in the initial authorization request.""" + return self._redirect_uri or "" + + @redirect_uri.setter + def redirect_uri(self, redirect_uri: Optional[str]): + if redirect_uri is None: + del self.redirect_uri + return + if not isinstance(redirect_uri, str): + raise TypeError('redirect_uri must be of type str') + self._redirect_uri = redirect_uri + + @property + def resource(self) -> str: + r""" + The 'resource' parameter is not part of the OAuth2 spec, but is sent by + some clients. We are capturing it here to avoid 'invalid field' errors. + """ + return self._resource or "" + + @resource.setter + def resource(self, resource: Optional[str]): + if resource is None: + del self.resource + return + if not isinstance(resource, str): + raise TypeError('resource must be of type str') + self._resource = resource + + @property + def refresh_token(self) -> str: + r""" + This field is used by MCP clients to refresh an access token. The client + sends a refresh_token to the standard /token endpoint, and this field + allows the server to correctly deserialize the request. + """ + return self._refresh_token or "" + + @refresh_token.setter + def refresh_token(self, refresh_token: Optional[str]): + if refresh_token is None: + del self.refresh_token + return + if not isinstance(refresh_token, str): + raise TypeError('refresh_token must be of type str') + self._refresh_token = refresh_token + def endpoint(self): path = '/api/v1/oauth2/token' return path.format_map(self.to_field_map(self)) @@ -100,6 +178,8 @@ class ExchangeOAuthTokenResponse(KaggleObject): Username of the user who authorized/owns this token. user_id (int) Id the of user who authorized/owns this token. + scope (str) + The scope of the access token as a space-delimited list of strings. """ def __init__(self): @@ -109,6 +189,7 @@ def __init__(self): self._expires_in = 0 self._username = "" self._user_id = 0 + self._scope = None self._freeze() @property @@ -198,6 +279,20 @@ def user_id(self, user_id: int): raise TypeError('user_id must be of type int') self._user_id = user_id + @property + def scope(self) -> str: + """The scope of the access token as a space-delimited list of strings.""" + return self._scope or "" + + @scope.setter + def scope(self, scope: Optional[str]): + if scope is None: + del self.scope + return + if not isinstance(scope, str): + raise TypeError('scope must be of type str') + self._scope = scope + @property def accessToken(self): return self.access_token @@ -385,6 +480,426 @@ def userId(self): return self.user_id +class RegisterOAuthClientRequest(KaggleObject): + r""" + Attributes: + client_name (str) + Human-readable name for the client (e.g., 'Gemini CLI MCP Client') + redirect_uris (str) + Array of redirect URIs the client will use (e.g., + 'http://localhost:7777/oauth/callback') + grant_types (str) + Array of OAuth 2.0 grant types the client requests (expected: + authorization_code, refresh_token) + token_endpoint_auth_method (str) + Client Authentication method (expected: 'none') + scope (str) + Space-separated list of scopes the client would like to use + code_challenge_method (str) + Array of supported PKCE methods (expected: 'S256') + """ + + def __init__(self): + self._client_name = "" + self._redirect_uris = [] + self._grant_types = [] + self._token_endpoint_auth_method = "" + self._scope = None + self._code_challenge_method = [] + self._freeze() + + @property + def client_name(self) -> str: + """Human-readable name for the client (e.g., 'Gemini CLI MCP Client')""" + return self._client_name + + @client_name.setter + def client_name(self, client_name: str): + if client_name is None: + del self.client_name + return + if not isinstance(client_name, str): + raise TypeError('client_name must be of type str') + self._client_name = client_name + + @property + def redirect_uris(self) -> Optional[List[str]]: + r""" + Array of redirect URIs the client will use (e.g., + 'http://localhost:7777/oauth/callback') + """ + return self._redirect_uris + + @redirect_uris.setter + def redirect_uris(self, redirect_uris: Optional[List[str]]): + if redirect_uris is None: + del self.redirect_uris + return + if not isinstance(redirect_uris, list): + raise TypeError('redirect_uris must be of type list') + if not all([isinstance(t, str) for t in redirect_uris]): + raise TypeError('redirect_uris must contain only items of type str') + self._redirect_uris = redirect_uris + + @property + def grant_types(self) -> Optional[List[str]]: + r""" + Array of OAuth 2.0 grant types the client requests (expected: + authorization_code, refresh_token) + """ + return self._grant_types + + @grant_types.setter + def grant_types(self, grant_types: Optional[List[str]]): + if grant_types is None: + del self.grant_types + return + if not isinstance(grant_types, list): + raise TypeError('grant_types must be of type list') + if not all([isinstance(t, str) for t in grant_types]): + raise TypeError('grant_types must contain only items of type str') + self._grant_types = grant_types + + @property + def token_endpoint_auth_method(self) -> str: + """Client Authentication method (expected: 'none')""" + return self._token_endpoint_auth_method + + @token_endpoint_auth_method.setter + def token_endpoint_auth_method(self, token_endpoint_auth_method: str): + if token_endpoint_auth_method is None: + del self.token_endpoint_auth_method + return + if not isinstance(token_endpoint_auth_method, str): + raise TypeError('token_endpoint_auth_method must be of type str') + self._token_endpoint_auth_method = token_endpoint_auth_method + + @property + def scope(self) -> str: + """Space-separated list of scopes the client would like to use""" + return self._scope or "" + + @scope.setter + def scope(self, scope: Optional[str]): + if scope is None: + del self.scope + return + if not isinstance(scope, str): + raise TypeError('scope must be of type str') + self._scope = scope + + @property + def code_challenge_method(self) -> Optional[List[str]]: + """Array of supported PKCE methods (expected: 'S256')""" + return self._code_challenge_method + + @code_challenge_method.setter + def code_challenge_method(self, code_challenge_method: Optional[List[str]]): + if code_challenge_method is None: + del self.code_challenge_method + return + if not isinstance(code_challenge_method, list): + raise TypeError('code_challenge_method must be of type list') + if not all([isinstance(t, str) for t in code_challenge_method]): + raise TypeError('code_challenge_method must contain only items of type str') + self._code_challenge_method = code_challenge_method + + def endpoint(self): + path = '/api/v1/oauth2/register' + return path.format_map(self.to_field_map(self)) + + + @staticmethod + def method(): + return 'POST' + + @staticmethod + def body_fields(): + return '*' + + +class RegisterOAuthClientResponse(KaggleObject): + r""" + According to RFC 7591 && + https://github.com/google-gemini/gemini-cli/blob/56f394cefd04696a5192fef9bbff8ba0e5b0583f/packages/core/src/mcp/oauth-provider.ts#L69 + + Attributes: + client_id (str) + redirect_uris (str) + grant_types (str) + response_types (str) + token_endpoint_auth_method (str) + scope (str) + authorization_url (str) + token_url (str) + client_secret (str) + client_id_issued_at (int) + client_secret_expires_at (int) + revocation_url (str) + userinfo_url (str) + code_challenge_methods_supported (str) + """ + + def __init__(self): + self._client_id = "" + self._redirect_uris = [] + self._grant_types = [] + self._response_types = [] + self._token_endpoint_auth_method = "" + self._scope = None + self._authorization_url = None + self._token_url = None + self._client_secret = None + self._client_id_issued_at = None + self._client_secret_expires_at = None + self._revocation_url = None + self._userinfo_url = None + self._code_challenge_methods_supported = [] + self._freeze() + + @property + def client_id(self) -> str: + return self._client_id + + @client_id.setter + def client_id(self, client_id: str): + if client_id is None: + del self.client_id + return + if not isinstance(client_id, str): + raise TypeError('client_id must be of type str') + self._client_id = client_id + + @property + def redirect_uris(self) -> Optional[List[str]]: + return self._redirect_uris + + @redirect_uris.setter + def redirect_uris(self, redirect_uris: Optional[List[str]]): + if redirect_uris is None: + del self.redirect_uris + return + if not isinstance(redirect_uris, list): + raise TypeError('redirect_uris must be of type list') + if not all([isinstance(t, str) for t in redirect_uris]): + raise TypeError('redirect_uris must contain only items of type str') + self._redirect_uris = redirect_uris + + @property + def grant_types(self) -> Optional[List[str]]: + return self._grant_types + + @grant_types.setter + def grant_types(self, grant_types: Optional[List[str]]): + if grant_types is None: + del self.grant_types + return + if not isinstance(grant_types, list): + raise TypeError('grant_types must be of type list') + if not all([isinstance(t, str) for t in grant_types]): + raise TypeError('grant_types must contain only items of type str') + self._grant_types = grant_types + + @property + def response_types(self) -> Optional[List[str]]: + return self._response_types + + @response_types.setter + def response_types(self, response_types: Optional[List[str]]): + if response_types is None: + del self.response_types + return + if not isinstance(response_types, list): + raise TypeError('response_types must be of type list') + if not all([isinstance(t, str) for t in response_types]): + raise TypeError('response_types must contain only items of type str') + self._response_types = response_types + + @property + def token_endpoint_auth_method(self) -> str: + return self._token_endpoint_auth_method + + @token_endpoint_auth_method.setter + def token_endpoint_auth_method(self, token_endpoint_auth_method: str): + if token_endpoint_auth_method is None: + del self.token_endpoint_auth_method + return + if not isinstance(token_endpoint_auth_method, str): + raise TypeError('token_endpoint_auth_method must be of type str') + self._token_endpoint_auth_method = token_endpoint_auth_method + + @property + def scope(self) -> str: + return self._scope or "" + + @scope.setter + def scope(self, scope: Optional[str]): + if scope is None: + del self.scope + return + if not isinstance(scope, str): + raise TypeError('scope must be of type str') + self._scope = scope + + @property + def authorization_url(self) -> str: + return self._authorization_url or "" + + @authorization_url.setter + def authorization_url(self, authorization_url: Optional[str]): + if authorization_url is None: + del self.authorization_url + return + if not isinstance(authorization_url, str): + raise TypeError('authorization_url must be of type str') + self._authorization_url = authorization_url + + @property + def token_url(self) -> str: + return self._token_url or "" + + @token_url.setter + def token_url(self, token_url: Optional[str]): + if token_url is None: + del self.token_url + return + if not isinstance(token_url, str): + raise TypeError('token_url must be of type str') + self._token_url = token_url + + @property + def revocation_url(self) -> str: + return self._revocation_url or "" + + @revocation_url.setter + def revocation_url(self, revocation_url: Optional[str]): + if revocation_url is None: + del self.revocation_url + return + if not isinstance(revocation_url, str): + raise TypeError('revocation_url must be of type str') + self._revocation_url = revocation_url + + @property + def userinfo_url(self) -> str: + return self._userinfo_url or "" + + @userinfo_url.setter + def userinfo_url(self, userinfo_url: Optional[str]): + if userinfo_url is None: + del self.userinfo_url + return + if not isinstance(userinfo_url, str): + raise TypeError('userinfo_url must be of type str') + self._userinfo_url = userinfo_url + + @property + def code_challenge_methods_supported(self) -> Optional[List[str]]: + return self._code_challenge_methods_supported + + @code_challenge_methods_supported.setter + def code_challenge_methods_supported(self, code_challenge_methods_supported: Optional[List[str]]): + if code_challenge_methods_supported is None: + del self.code_challenge_methods_supported + return + if not isinstance(code_challenge_methods_supported, list): + raise TypeError('code_challenge_methods_supported must be of type list') + if not all([isinstance(t, str) for t in code_challenge_methods_supported]): + raise TypeError('code_challenge_methods_supported must contain only items of type str') + self._code_challenge_methods_supported = code_challenge_methods_supported + + @property + def client_secret(self) -> str: + return self._client_secret or "" + + @client_secret.setter + def client_secret(self, client_secret: Optional[str]): + if client_secret is None: + del self.client_secret + return + if not isinstance(client_secret, str): + raise TypeError('client_secret must be of type str') + self._client_secret = client_secret + + @property + def client_id_issued_at(self) -> int: + return self._client_id_issued_at or 0 + + @client_id_issued_at.setter + def client_id_issued_at(self, client_id_issued_at: Optional[int]): + if client_id_issued_at is None: + del self.client_id_issued_at + return + if not isinstance(client_id_issued_at, int): + raise TypeError('client_id_issued_at must be of type int') + self._client_id_issued_at = client_id_issued_at + + @property + def client_secret_expires_at(self) -> int: + return self._client_secret_expires_at or 0 + + @client_secret_expires_at.setter + def client_secret_expires_at(self, client_secret_expires_at: Optional[int]): + if client_secret_expires_at is None: + del self.client_secret_expires_at + return + if not isinstance(client_secret_expires_at, int): + raise TypeError('client_secret_expires_at must be of type int') + self._client_secret_expires_at = client_secret_expires_at + + @property + def clientId(self): + return self.client_id + + @property + def redirectUris(self): + return self.redirect_uris + + @property + def grantTypes(self): + return self.grant_types + + @property + def responseTypes(self): + return self.response_types + + @property + def tokenEndpointAuthMethod(self): + return self.token_endpoint_auth_method + + @property + def authorizationUrl(self): + return self.authorization_url + + @property + def tokenUrl(self): + return self.token_url + + @property + def revocationUrl(self): + return self.revocation_url + + @property + def userinfoUrl(self): + return self.userinfo_url + + @property + def codeChallengeMethodsSupported(self): + return self.code_challenge_methods_supported + + @property + def clientSecret(self): + return self.client_secret + + @property + def clientIdIssuedAt(self): + return self.client_id_issued_at + + @property + def clientSecretExpiresAt(self): + return self.client_secret_expires_at + + class StartOAuthFlowRequest(KaggleObject): r""" Attributes: @@ -417,6 +932,9 @@ class StartOAuthFlowRequest(KaggleObject): response_mode (str) Mode of the OAuth flow completed response. Must be set to 'query', which means response will be sent as query string parameters. + resource (str) + The 'resource' parameter is not part of the OAuth2 spec, but is sent by + some clients. We are capturing it here to avoid 'invalid field' errors. """ def __init__(self): @@ -428,6 +946,7 @@ def __init__(self): self._code_challenge_method = None self._response_type = "" self._response_mode = "" + self._resource = None self._freeze() @property @@ -567,15 +1086,36 @@ def response_mode(self, response_mode: str): raise TypeError('response_mode must be of type str') self._response_mode = response_mode + @property + def resource(self) -> str: + r""" + The 'resource' parameter is not part of the OAuth2 spec, but is sent by + some clients. We are capturing it here to avoid 'invalid field' errors. + """ + return self._resource or "" + + @resource.setter + def resource(self, resource: Optional[str]): + if resource is None: + del self.resource + return + if not isinstance(resource, str): + raise TypeError('resource must be of type str') + self._resource = resource + def endpoint(self): path = '/api/v1/oauth2/authorize' return path.format_map(self.to_field_map(self)) ExchangeOAuthTokenRequest._fields = [ - FieldMetadata("code", "code", "_code", str, "", PredefinedSerializer()), + FieldMetadata("code", "code", "_code", str, None, PredefinedSerializer(), optional=True), FieldMetadata("codeVerifier", "code_verifier", "_code_verifier", str, None, PredefinedSerializer(), optional=True), FieldMetadata("grantType", "grant_type", "_grant_type", str, "", PredefinedSerializer()), + FieldMetadata("clientId", "client_id", "_client_id", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("redirectUri", "redirect_uri", "_redirect_uri", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("resource", "resource", "_resource", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("refreshToken", "refresh_token", "_refresh_token", str, None, PredefinedSerializer(), optional=True), ] ExchangeOAuthTokenResponse._fields = [ @@ -585,6 +1125,7 @@ def endpoint(self): FieldMetadata("expiresIn", "expires_in", "_expires_in", int, 0, PredefinedSerializer()), FieldMetadata("username", "username", "_username", str, "", PredefinedSerializer()), FieldMetadata("userId", "user_id", "_user_id", int, 0, PredefinedSerializer()), + FieldMetadata("scope", "scope", "_scope", str, None, PredefinedSerializer(), optional=True), ] IntrospectTokenRequest._fields = [ @@ -600,6 +1141,32 @@ def endpoint(self): FieldMetadata("exp", "exp", "_exp", int, None, PredefinedSerializer(), optional=True), ] +RegisterOAuthClientRequest._fields = [ + FieldMetadata("clientName", "client_name", "_client_name", str, "", PredefinedSerializer()), + FieldMetadata("redirectUris", "redirect_uris", "_redirect_uris", str, [], ListSerializer(PredefinedSerializer())), + FieldMetadata("grantTypes", "grant_types", "_grant_types", str, [], ListSerializer(PredefinedSerializer())), + FieldMetadata("tokenEndpointAuthMethod", "token_endpoint_auth_method", "_token_endpoint_auth_method", str, "", PredefinedSerializer()), + FieldMetadata("scope", "scope", "_scope", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("codeChallengeMethod", "code_challenge_method", "_code_challenge_method", str, [], ListSerializer(PredefinedSerializer())), +] + +RegisterOAuthClientResponse._fields = [ + FieldMetadata("client_id", "client_id", "_client_id", str, "", PredefinedSerializer()), + FieldMetadata("redirect_uris", "redirect_uris", "_redirect_uris", str, [], ListSerializer(PredefinedSerializer())), + FieldMetadata("grant_types", "grant_types", "_grant_types", str, [], ListSerializer(PredefinedSerializer())), + FieldMetadata("response_types", "response_types", "_response_types", str, [], ListSerializer(PredefinedSerializer())), + FieldMetadata("token_endpoint_auth_method", "token_endpoint_auth_method", "_token_endpoint_auth_method", str, "", PredefinedSerializer()), + FieldMetadata("scope", "scope", "_scope", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("authorization_url", "authorization_url", "_authorization_url", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("token_url", "token_url", "_token_url", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("client_secret", "client_secret", "_client_secret", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("client_id_issued_at", "client_id_issued_at", "_client_id_issued_at", int, None, PredefinedSerializer(), optional=True), + FieldMetadata("client_secret_expires_at", "client_secret_expires_at", "_client_secret_expires_at", int, None, PredefinedSerializer(), optional=True), + FieldMetadata("revocation_url", "revocation_url", "_revocation_url", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("userinfo_url", "userinfo_url", "_userinfo_url", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("code_challenge_methods_supported", "code_challenge_methods_supported", "_code_challenge_methods_supported", str, [], ListSerializer(PredefinedSerializer())), +] + StartOAuthFlowRequest._fields = [ FieldMetadata("clientId", "client_id", "_client_id", str, "", PredefinedSerializer()), FieldMetadata("redirectUri", "redirect_uri", "_redirect_uri", str, "", PredefinedSerializer()), @@ -609,5 +1176,6 @@ def endpoint(self): FieldMetadata("codeChallengeMethod", "code_challenge_method", "_code_challenge_method", str, None, PredefinedSerializer(), optional=True), FieldMetadata("responseType", "response_type", "_response_type", str, "", PredefinedSerializer()), FieldMetadata("responseMode", "response_mode", "_response_mode", str, "", PredefinedSerializer()), + FieldMetadata("resource", "resource", "_resource", str, None, PredefinedSerializer(), optional=True), ] diff --git a/src/kagglesdk/users/services/group_api_service.py b/src/kagglesdk/users/services/group_api_service.py index caa62ef6..518dbcf9 100644 --- a/src/kagglesdk/users/services/group_api_service.py +++ b/src/kagglesdk/users/services/group_api_service.py @@ -1,5 +1,5 @@ from kagglesdk.kaggle_http_client import KaggleHttpClient -from kagglesdk.users.types.group_api_service import ApiListUserManagedGroupMembershipsRequest, ApiListUserManagedGroupMembershipsResponse +from kagglesdk.users.types.group_api_service import ApiListSynchronizedGroupMembershipsRequest, ApiListSynchronizedGroupMembershipsResponse, ApiListUserManagedGroupMembershipsRequest, ApiListUserManagedGroupMembershipsResponse class GroupApiClient(object): @@ -17,3 +17,15 @@ def list_user_managed_group_memberships(self, request: ApiListUserManagedGroupMe request = ApiListUserManagedGroupMembershipsRequest() return self._client.call("users.GroupApiService", "ListUserManagedGroupMemberships", request, ApiListUserManagedGroupMembershipsResponse) + + def list_synchronized_group_memberships(self, request: ApiListSynchronizedGroupMembershipsRequest = None) -> ApiListSynchronizedGroupMembershipsResponse: + r""" + Args: + request (ApiListSynchronizedGroupMembershipsRequest): + The request object; initialized to empty instance if not specified. + """ + + if request is None: + request = ApiListSynchronizedGroupMembershipsRequest() + + return self._client.call("users.GroupApiService", "ListSynchronizedGroupMemberships", request, ApiListSynchronizedGroupMembershipsResponse) diff --git a/src/kagglesdk/users/types/group_api_service.py b/src/kagglesdk/users/types/group_api_service.py index 945b7dee..9e7291a5 100644 --- a/src/kagglesdk/users/types/group_api_service.py +++ b/src/kagglesdk/users/types/group_api_service.py @@ -1,6 +1,167 @@ from kagglesdk.kaggle_object import * from typing import Optional, List +class ApiGroupMembership(KaggleObject): + r""" + Attributes: + user_id (int) + username (str) + """ + + def __init__(self): + self._user_id = 0 + self._username = "" + self._freeze() + + @property + def user_id(self) -> int: + return self._user_id + + @user_id.setter + def user_id(self, user_id: int): + if user_id is None: + del self.user_id + return + if not isinstance(user_id, int): + raise TypeError('user_id must be of type int') + self._user_id = user_id + + @property + def username(self) -> str: + return self._username + + @username.setter + def username(self, username: str): + if username is None: + del self.username + return + if not isinstance(username, str): + raise TypeError('username must be of type str') + self._username = username + + +class ApiListSynchronizedGroupMembershipsRequest(KaggleObject): + r""" + Attributes: + page_size (int) + page_token (str) + skip (int) + group_slug (str) + """ + + def __init__(self): + self._page_size = 0 + self._page_token = None + self._skip = None + self._group_slug = "" + self._freeze() + + @property + def page_size(self) -> int: + return self._page_size + + @page_size.setter + def page_size(self, page_size: int): + if page_size is None: + del self.page_size + return + if not isinstance(page_size, int): + raise TypeError('page_size must be of type int') + self._page_size = page_size + + @property + def page_token(self) -> str: + return self._page_token or "" + + @page_token.setter + def page_token(self, page_token: Optional[str]): + if page_token is None: + del self.page_token + return + if not isinstance(page_token, str): + raise TypeError('page_token must be of type str') + self._page_token = page_token + + @property + def skip(self) -> int: + return self._skip or 0 + + @skip.setter + def skip(self, skip: Optional[int]): + if skip is None: + del self.skip + return + if not isinstance(skip, int): + raise TypeError('skip must be of type int') + self._skip = skip + + @property + def group_slug(self) -> str: + return self._group_slug + + @group_slug.setter + def group_slug(self, group_slug: str): + if group_slug is None: + del self.group_slug + return + if not isinstance(group_slug, str): + raise TypeError('group_slug must be of type str') + self._group_slug = group_slug + + def endpoint(self): + path = '/api/v1/sync_groups/{group_slug}/members' + return path.format_map(self.to_field_map(self)) + + @staticmethod + def endpoint_path(): + return '/api/v1/sync_groups/{group_slug}/members' + + +class ApiListSynchronizedGroupMembershipsResponse(KaggleObject): + r""" + Attributes: + memberships (ApiGroupMembership) + next_page_token (str) + """ + + def __init__(self): + self._memberships = [] + self._next_page_token = "" + self._freeze() + + @property + def memberships(self) -> Optional[List[Optional['ApiGroupMembership']]]: + return self._memberships + + @memberships.setter + def memberships(self, memberships: Optional[List[Optional['ApiGroupMembership']]]): + if memberships is None: + del self.memberships + return + if not isinstance(memberships, list): + raise TypeError('memberships must be of type list') + if not all([isinstance(t, ApiGroupMembership) for t in memberships]): + raise TypeError('memberships must contain only items of type ApiGroupMembership') + self._memberships = memberships + + @property + def next_page_token(self) -> str: + return self._next_page_token + + @next_page_token.setter + def next_page_token(self, next_page_token: str): + if next_page_token is None: + del self.next_page_token + return + if not isinstance(next_page_token, str): + raise TypeError('next_page_token must be of type str') + self._next_page_token = next_page_token + + @property + def nextPageToken(self): + return self.next_page_token + + class ApiListUserManagedGroupMembershipsRequest(KaggleObject): r""" Attributes: @@ -81,67 +242,28 @@ def endpoint_path(): class ApiListUserManagedGroupMembershipsResponse(KaggleObject): r""" Attributes: - memberships (ApiListUserManagedGroupMembershipsResponse.Membership) + memberships (ApiGroupMembership) next_page_token (str) """ - class Membership(KaggleObject): - r""" - Attributes: - user_id (int) - username (str) - """ - - def __init__(self): - self._user_id = 0 - self._username = "" - self._freeze() - - @property - def user_id(self) -> int: - return self._user_id - - @user_id.setter - def user_id(self, user_id: int): - if user_id is None: - del self.user_id - return - if not isinstance(user_id, int): - raise TypeError('user_id must be of type int') - self._user_id = user_id - - @property - def username(self) -> str: - return self._username - - @username.setter - def username(self, username: str): - if username is None: - del self.username - return - if not isinstance(username, str): - raise TypeError('username must be of type str') - self._username = username - - def __init__(self): self._memberships = [] self._next_page_token = "" self._freeze() @property - def memberships(self) -> Optional[List[Optional['ApiListUserManagedGroupMembershipsResponse.Membership']]]: + def memberships(self) -> Optional[List[Optional['ApiGroupMembership']]]: return self._memberships @memberships.setter - def memberships(self, memberships: Optional[List[Optional['ApiListUserManagedGroupMembershipsResponse.Membership']]]): + def memberships(self, memberships: Optional[List[Optional['ApiGroupMembership']]]): if memberships is None: del self.memberships return if not isinstance(memberships, list): raise TypeError('memberships must be of type list') - if not all([isinstance(t, ApiListUserManagedGroupMembershipsResponse.Membership) for t in memberships]): - raise TypeError('memberships must contain only items of type ApiListUserManagedGroupMembershipsResponse.Membership') + if not all([isinstance(t, ApiGroupMembership) for t in memberships]): + raise TypeError('memberships must contain only items of type ApiGroupMembership') self._memberships = memberships @property @@ -162,20 +284,32 @@ def nextPageToken(self): return self.next_page_token -ApiListUserManagedGroupMembershipsRequest._fields = [ +ApiGroupMembership._fields = [ + FieldMetadata("userId", "user_id", "_user_id", int, 0, PredefinedSerializer()), + FieldMetadata("username", "username", "_username", str, "", PredefinedSerializer()), +] + +ApiListSynchronizedGroupMembershipsRequest._fields = [ FieldMetadata("pageSize", "page_size", "_page_size", int, 0, PredefinedSerializer()), FieldMetadata("pageToken", "page_token", "_page_token", str, None, PredefinedSerializer(), optional=True), FieldMetadata("skip", "skip", "_skip", int, None, PredefinedSerializer(), optional=True), FieldMetadata("groupSlug", "group_slug", "_group_slug", str, "", PredefinedSerializer()), ] -ApiListUserManagedGroupMembershipsResponse.Membership._fields = [ - FieldMetadata("userId", "user_id", "_user_id", int, 0, PredefinedSerializer()), - FieldMetadata("username", "username", "_username", str, "", PredefinedSerializer()), +ApiListSynchronizedGroupMembershipsResponse._fields = [ + FieldMetadata("memberships", "memberships", "_memberships", ApiGroupMembership, [], ListSerializer(KaggleObjectSerializer())), + FieldMetadata("nextPageToken", "next_page_token", "_next_page_token", str, "", PredefinedSerializer()), +] + +ApiListUserManagedGroupMembershipsRequest._fields = [ + FieldMetadata("pageSize", "page_size", "_page_size", int, 0, PredefinedSerializer()), + FieldMetadata("pageToken", "page_token", "_page_token", str, None, PredefinedSerializer(), optional=True), + FieldMetadata("skip", "skip", "_skip", int, None, PredefinedSerializer(), optional=True), + FieldMetadata("groupSlug", "group_slug", "_group_slug", str, "", PredefinedSerializer()), ] ApiListUserManagedGroupMembershipsResponse._fields = [ - FieldMetadata("memberships", "memberships", "_memberships", ApiListUserManagedGroupMembershipsResponse.Membership, [], ListSerializer(KaggleObjectSerializer())), + FieldMetadata("memberships", "memberships", "_memberships", ApiGroupMembership, [], ListSerializer(KaggleObjectSerializer())), FieldMetadata("nextPageToken", "next_page_token", "_next_page_token", str, "", PredefinedSerializer()), ]