Skip to content

Commit

Permalink
add async class (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
joelee2012 committed May 29, 2023
1 parent a396d1e commit 57c3f7b
Show file tree
Hide file tree
Showing 35 changed files with 1,606 additions and 528 deletions.
374 changes: 334 additions & 40 deletions api4jenkins/__init__.py

Large diffs are not rendered by default.

97 changes: 95 additions & 2 deletions api4jenkins/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from .artifact import Artifact, save_response_to
from .input import PendingInputAction
from .item import Item
from .mix import ActionsMixIn, DeletionMixIn, DescriptionMixIn
from .item import AsyncItem, Item
from .mix import ActionsMixIn, AsyncActionsMixIn, AsyncDeletionMixIn, AsyncDescriptionMixIn, DeletionMixIn, DescriptionMixIn
from .report import CoverageReport, CoverageResult, CoverageTrends, TestReport


Expand Down Expand Up @@ -101,3 +101,96 @@ class FreeStyleBuild(Build):

class MatrixBuild(Build):
pass

# async class


class AsyncBuild(AsyncItem, AsyncDescriptionMixIn, AsyncDeletionMixIn, AsyncActionsMixIn):

async def console_text(self):
async with self.handle_stream('GET', 'consoleText') as resp:
async for line in resp.aiter_lines():
yield line

async def progressive_output(self, html=False):
url = 'logText/progressiveHtml' if html else 'logText/progressiveText'
start = 0
while True:
resp = await self.handle_req('GET', url, params={'start': start})
time.sleep(1)
if start == resp.headers.get('X-Text-Size'):
continue
async for line in resp.aiter_lines():
yield line
if not resp.headers.get('X-More-Data'):
break
start = resp.headers['X-Text-Size']

async def stop(self):
return await self.handle_req('POST', 'stop')

async def term(self):
return await self.handle_req('POST', 'term')

async def kill(self):
return await self.handle_req('POST', 'kill')

async def get_next_build(self):
item = await self.api_json(tree='nextBuild[url]')['nextBuild']
return self.__class__(self.jenkins, item['url']) if item else None

async def get_previous_build(self):
item = await self.api_json(tree='previousBuild[url]')['previousBuild']
return self.__class__(self.jenkins, item['url']) if item else None

async def get_job(self):
'''get job of this build'''
job_name = self.jenkins._url2name(re.sub(r'\w+[/]?$', '', self.url))
return await self.jenkins.get_job(job_name)

async def get_test_report(self):
tr = TestReport(self.jenkins, f'{self.url}testReport/')
return tr if tr.exists() else None

def get_coverage_report(self):
'''Access coverage report generated by `JaCoCo <https://plugins.jenkins.io/jacoco/>`_'''
cr = CoverageReport(self.jenkins, f'{self.url}jacoco/')
return cr if cr.exists() else None

def get_coverage_result(self):
'''Access coverage result generated by `Code Coverage API <https://plugins.jenkins.io/code-coverage-api/>`_'''
cr = CoverageResult(self.jenkins, f'{self.url}coverage/result/')
return cr if cr.exists() else None

def get_coverage_trends(self):
ct = CoverageTrends(self.jenkins, f'{self.url}coverage/trend/')
return ct if ct.exists() else None


class AsyncWorkflowRun(AsyncBuild):

async def get_pending_input(self):
'''get current pending input step'''
data = await self.handle_req('GET', 'wfapi/describe').json()
if not data['_links'].get('pendingInputActions'):
return None
action = await self.handle_req('GET', 'wfapi/pendingInputActions').json()[0]
action["abortUrl"] = action["abortUrl"][action["abortUrl"].index(
"/job/"):]
return PendingInputAction(self.jenkins, action)

async def get_artifacts(self):
artifacts = (await self.handle_req('GET', 'wfapi/artifacts')).json()
return [Artifact(self.jenkins, art) for art in artifacts]

async def save_artifacts(self, filename='archive.zip'):
async with self.handle_stream('GET', 'artifact/*zip*/archive.zip') as resp:
save_response_to(resp, filename)


class AsyncFreeStyleBuild(AsyncBuild):
pass


class AsyncMatrixBuild(AsyncBuild):
pass
29 changes: 27 additions & 2 deletions api4jenkins/credential.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# encoding: utf-8


from .item import Item
from .mix import ConfigurationMixIn, DeletionMixIn
from .item import Item, AsyncItem
from .mix import ConfigurationMixIn, DeletionMixIn, AsyncConfigurationMixIn, AsyncDeletionMixIn


class Credentials(Item):
Expand All @@ -26,3 +26,28 @@ def __iter__(self):

class Credential(Item, ConfigurationMixIn, DeletionMixIn):
pass


# async class

class AsyncCredentials(AsyncItem):

async def get(self, id):
for item in (await self.api_json(tree='credentials[id]'))['credentials']:
if item['id'] == id:
return AsyncCredential(self.jenkins,
f'{self.url}credential/{id}/')
return None

async def create(self, xml):
await self.handle_req('POST', 'createCredentials',
headers=self.headers, content=xml)

async def __aiter__(self):
for item in (await self.api_json(tree='credentials[id]'))['credentials']:
yield AsyncCredential(self.jenkins,
f'{self.url}credential/{item["id"]}/')


class AsyncCredential(AsyncItem, AsyncConfigurationMixIn, AsyncDeletionMixIn):
pass
1 change: 0 additions & 1 deletion api4jenkins/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# encoding: utf-8

class JenkinsAPIException(Exception):
pass
Expand Down
55 changes: 55 additions & 0 deletions api4jenkins/http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# encoding: utf-8
import logging
from httpx import Client, HTTPTransport, AsyncClient, AsyncHTTPTransport
from .exceptions import ItemNotFoundError, ServerError, AuthenticationError, BadRequestError
from .__version__ import __title__, __version__


logger = logging.getLogger(__name__)


def log_request(request):
logger.debug(
f"Send Request: {request.method} {request.url} - Waiting for response")


def check_response(response):
# request = response.request
# logger.debug(
# f"Response event hook: {request.method} {request.url} - Status {response.status_code}")
if response.is_success or response.is_redirect:
return
if response.status_code == 404:
raise ItemNotFoundError(f'404 Not found {response.url}')
if response.status_code == 401:
raise AuthenticationError(
f'401 Invalid authorization for {response.url}')
if response.status_code == 403:
raise PermissionError(f'403 No permission to access {response.url}')
if response.status_code == 400:
raise BadRequestError(f'400 {response.headers["X-Error"]}')
response.raise_for_status()


def new_http_client(**kwargs):
transport = HTTPTransport(retries=kwargs.pop('max_retries', 1))
client = Client(transport=transport, **kwargs,
event_hooks={'request': [log_request], 'response': [check_response]})
client.headers = {'User-Agent': f'{__title__}/{__version__}'}
return client


async def alog_request(request):
log_request(request)


async def acheck_response(response):
check_response(response)


def new_async_http_client(**kwargs):
transport = AsyncHTTPTransport(retries=kwargs.pop('max_retries', 1))
client = AsyncClient(transport=transport, **kwargs,
event_hooks={'request': [alog_request], 'response': [acheck_response]})
client.headers = {'User-Agent': f'{__title__}/{__version__}'}
return client

0 comments on commit 57c3f7b

Please sign in to comment.