backend/python/plugins/azuredevops/azuredevops/api.py (53 lines of code) (raw):

# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Optional import base64 from pydevlake.api import API, APIException, Paginator, Request, Response, request_hook, response_hook class AzurePaginator(Paginator): def get_items(self, response) -> Optional[list[object]]: return response.json['value'] def get_next_page_id(self, response) -> Optional[str]: return response.headers.get('x-ms-continuationtoken') def set_next_page_param(self, request, next_page_id): request.query_args['continuationToken'] = next_page_id class AzureDevOpsAPI(API): paginator = AzurePaginator() base_url = "https://dev.azure.com/" @request_hook def authenticate(self, request: Request): token_b64 = base64.b64encode((':' + self.connection.token.get_secret_value()).encode()).decode() request.headers['Authorization'] = 'Basic ' + token_b64 @request_hook def set_api_version(self, request: Request): request.query_args['api-version'] = "7.0" @response_hook def change_203_to_401(self, response: Response): # When the token is invalid, Azure DevOps returns a 302 that resolves to a sign-in page with status 203 # We want to change that to a 401 and raise an exception if response.status == 203: response.status = 401 raise APIException(response) def my_profile(self): req = Request('https://app.vssps.visualstudio.com/_apis/profile/profiles/me') return self.send(req) def accounts(self, member_id: str): req = Request('https://app.vssps.visualstudio.com/_apis/accounts', query_args={"memberId": member_id}) return self.send(req) def projects(self, org: str): return self.get(org, '_apis/projects') def git_repos(self, org: str, project: str): return self.get(org, project, '_apis/git/repositories') def git_repo_pull_requests(self, org: str, project: str, repo_id: str): return self.get(org, project, '_apis/git/repositories', repo_id, 'pullrequests?searchCriteria.status=all') def git_repo_pull_request_commits(self, org: str, project: str, repo_id: str, pull_request_id: int): return self.get(org, project, '_apis/git/repositories', repo_id, 'pullRequests', pull_request_id, 'commits') def git_repo_pull_request_comments(self, org: str, project: str, repo_id: str, pull_request_id: int): return self.get(org, project, '_apis/git/repositories', repo_id, 'pullRequests', pull_request_id, 'threads') def commits(self, org: str, project: str, repo_id: str): return self.get(org, project, '_apis/git/repositories', repo_id, 'commits') def builds(self, org: str, project: str, repository_id: str, provider: str): return self.get(org, project, '_apis/build/builds', repositoryId=repository_id, repositoryType=provider, deletedFilter='excludeDeleted', queryOrder="finishTimeDescending") def jobs(self, org: str, project: str, build_id: int): return self.get(org, project, '_apis/build/builds', build_id, 'timeline') def endpoints(self, org: str, project: str): return self.get(org, project, '_apis/serviceendpoint/endpoints') def external_repositories(self, org: str, project: str, provider: str, endpoint_id: str): return self.get(org, project, '_apis/sourceProviders', provider, 'repositories', serviceEndpointId=endpoint_id)