diff --git a/jenkins_epo/jenkins.py b/jenkins_epo/jenkins.py
index f28f28c7..695bd6aa 100644
--- a/jenkins_epo/jenkins.py
+++ b/jenkins_epo/jenkins.py
@@ -13,24 +13,21 @@
# jenkins-epo. If not, see .
-import ast
import asyncio
from datetime import datetime, timedelta
from itertools import product
import logging
import re
-import aiohttp
from jenkinsapi.jenkinsbase import JenkinsBase
from jenkinsapi.jenkins import Jenkins, Requester
from jenkins_yml import Job as JobSpec
import yaml
-from yarl import URL
from .settings import SETTINGS
from .utils import format_duration, match, parse_patterns, retry
from .web import fullurl
-
+from . import rest
logger = logging.getLogger(__name__)
@@ -39,56 +36,17 @@ class NotOnJenkins(Exception):
pass
-class RESTClient(object):
- def __init__(self, path=''):
- self.path = path
-
- def __call__(self, arg):
- return self.__class__(self.path.lstrip('/') + '/' + str(arg))
-
- def __getattr__(self, name):
- return self(name)
-
- @retry
- def afetch(self, **kw):
- session = aiohttp.ClientSession()
- url = URL(self.path)
- if kw:
- url = url.with_query(**kw)
- logger.debug("GET %s", url)
- try:
- response = yield from session.get(url, timeout=10)
- payload = yield from response.read()
- finally:
- yield from session.close()
- response.raise_for_status()
- return payload.decode('utf-8')
-
- def aget(self, **kw):
- payload = yield from self.api.python.afetch(**kw)
- return ast.literal_eval(payload)
-
- @retry
- def apost(self, **kw):
- session = aiohttp.ClientSession()
- url = URL(self.path)
- if kw:
- url = url.with_query(**kw)
- logger.debug("POST %s", url)
- try:
- response = yield from session.post(url, timeout=10)
- payload = yield from response.read()
- finally:
- yield from session.close()
- response.raise_for_status()
- return payload.decode('utf-8')
-
-
class VerboseRequester(Requester):
def get_url(self, url, *a, **kw):
logger.debug("GET %s (sync)", url)
return super(VerboseRequester, self).get_url(url, *a, **kw)
+ def post_url(self, url, *a, **kw):
+ logger.debug("POST %s (sync)", url)
+ return super(VerboseRequester, self).post_url(
+ url, *a, **kw
+ )
+
# Monkey patch poll=True to avoid I/O in __init__
JenkinsBase.__init__.__defaults__ = (False,)
@@ -109,7 +67,7 @@ def __getattr__(self, name):
def load(self):
if not self._instance:
logger.debug("Connecting to Jenkins %s", SETTINGS.JENKINS_URL)
- self.rest = RESTClient(SETTINGS.JENKINS_URL)
+ self.rest = rest.Client(SETTINGS.JENKINS_URL)
self._instance = Jenkins(
baseurl=SETTINGS.JENKINS_URL,
requester=VerboseRequester(baseurl=SETTINGS.JENKINS_URL),
@@ -118,7 +76,7 @@ def load(self):
@asyncio.coroutine
def is_queue_empty(self):
- payload = yield from self.rest.queue.aget()
+ payload = yield from self.rest.queue.api.python.aget()
items = [
i for i in payload['items']
if not i['stuck'] and match(i['task']['name'], self.queue_patterns)
@@ -137,9 +95,10 @@ def get_job(self, name):
def aget_job(self, name):
self.load()
instance = self._instance.get_job(name)
- client = RESTClient(instance.baseurl)
- instance._data = yield from client.aget()
- instance._config = yield from client('config.xml').afetch()
+ client = rest.Client(instance.baseurl)
+ instance._data = yield from client.api.python.aget()
+ payload = yield from client('config.xml').aget()
+ instance._config = payload.data
return Job.factory(instance)
DESCRIPTION_TMPL = """\
@@ -234,7 +193,9 @@ def from_url(cls, url):
if not url.startswith(SETTINGS.JENKINS_URL):
raise NotOnJenkins("%s is not on this Jenkins." % url)
- payload = yield from RESTClient(url).aget(tree=cls.jenkins_tree)
+ payload = yield from rest.Client(url).api.python.aget(
+ tree=cls.jenkins_tree,
+ )
return Build(None, payload)
def __getattr__(self, name):
@@ -310,7 +271,7 @@ def sha(self):
@asyncio.coroutine
def stop(self):
- payload = yield from RESTClient(self.payload['url']).stop.apost()
+ payload = yield from rest.Client(self.payload['url']).stop.apost()
return payload
@@ -409,7 +370,9 @@ def node_param(self):
@asyncio.coroutine
def fetch_builds(self):
tree = "builds[" + Build.jenkins_tree + "]"
- payload = yield from RESTClient(self.baseurl).aget(tree=tree)
+ payload = yield from rest.Client(self.baseurl).api.python.aget(
+ tree=tree,
+ )
return payload['builds']
def process_builds(self, payload):
diff --git a/jenkins_epo/rest.py b/jenkins_epo/rest.py
new file mode 100644
index 00000000..b8c718b7
--- /dev/null
+++ b/jenkins_epo/rest.py
@@ -0,0 +1,102 @@
+# This file is part of jenkins-epo
+#
+# jenkins-epo is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or any later version.
+#
+# jenkins-epo is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# jenkins-epo. If not, see .
+
+import ast
+import collections
+import logging
+
+import aiohttp
+from yarl import URL
+
+from .utils import retry
+
+
+logger = logging.getLogger(__name__)
+
+
+class Payload(object):
+ @classmethod
+ def factory(cls, status, headers, payload):
+ if isinstance(payload, list):
+ return PayloadList(status, headers, payload)
+ elif isinstance(payload, dict):
+ return PayloadDict(status, headers, payload)
+ elif isinstance(payload, str):
+ return PayloadString(status, headers, payload)
+ else:
+ raise Exception("Unhandled payload type")
+
+ def __init__(self, status, headers, payload):
+ super(Payload, self).__init__(payload)
+ self.status = status
+ self.headers = headers
+
+
+class PayloadList(Payload, collections.UserList):
+ pass
+
+
+class PayloadDict(Payload, collections.UserDict):
+ pass
+
+
+class PayloadString(Payload, collections.UserString):
+ pass
+
+
+class Client(object):
+ def __init__(self, url=''):
+ self.url = url
+
+ def __call__(self, url):
+ if not url.startswith('http://'):
+ url = self.url.rstrip('/') + '/' + str(url)
+ return self.__class__(url)
+
+ def __getattr__(self, name):
+ return self(name)
+
+ @retry
+ def aget(self, **kw):
+ session = aiohttp.ClientSession()
+ url = URL(self.url)
+ if kw:
+ url = url.with_query(**kw)
+ logger.debug("GET %s", url)
+ try:
+ response = yield from session.get(url, timeout=10)
+ payload = yield from response.read()
+ finally:
+ yield from session.close()
+ response.raise_for_status()
+ payload = payload.decode('utf-8')
+ if response.content_type == 'text/x-python':
+ payload = ast.literal_eval(payload)
+ return Payload.factory(response.status, response.headers, payload)
+
+ @retry
+ def apost(self, **kw):
+ session = aiohttp.ClientSession()
+ url = URL(self.url)
+ if kw:
+ url = url.with_query(**kw)
+ logger.debug("POST %s", url)
+ try:
+ response = yield from session.post(url, timeout=10)
+ payload = yield from response.read()
+ finally:
+ yield from session.close()
+ response.raise_for_status()
+ payload = payload.decode('utf-8')
+ return Payload.factory(response.status, response.headers, payload)
diff --git a/tests/test_build.py b/tests/test_build.py
index 4368a994..df22a907 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -9,14 +9,14 @@
@asyncio.coroutine
def test_from_url(SETTINGS, mocker):
SETTINGS.JENKINS_URL = 'jenkins://'
- RESTClient = mocker.patch('jenkins_epo.jenkins.RESTClient')
+ Client = mocker.patch('jenkins_epo.jenkins.rest.Client')
from jenkins_epo.jenkins import Build, NotOnJenkins
with pytest.raises(NotOnJenkins):
yield from Build.from_url('circleci:///')
- RESTClient().aget = CoroutineMock(return_value=dict(number=1))
+ Client().api.python.aget = CoroutineMock(return_value=dict(number=1))
build = yield from Build.from_url('jenkins://job/1')
@@ -112,13 +112,13 @@ def test_commit_status():
@pytest.mark.asyncio
@asyncio.coroutine
def test_stop(SETTINGS, mocker):
- RESTClient = mocker.patch('jenkins_epo.jenkins.RESTClient')
+ Client = mocker.patch('jenkins_epo.jenkins.rest.Client')
from jenkins_epo.jenkins import Build
build = Build(Mock(), payload=dict(url='jenkins://'))
- RESTClient().stop.apost = CoroutineMock()
+ Client().stop.apost = CoroutineMock()
yield from build.stop()
- assert RESTClient().stop.apost.mock_calls
+ assert Client().stop.apost.mock_calls
diff --git a/tests/test_jenkins.py b/tests/test_jenkins.py
index 96ba9499..b8a93ce9 100644
--- a/tests/test_jenkins.py
+++ b/tests/test_jenkins.py
@@ -1,6 +1,6 @@
import asyncio
-from asynctest import patch, CoroutineMock, Mock
+from asynctest import patch, CoroutineMock, Mock
import pytest
@@ -16,16 +16,20 @@ def test_lazy_load(mocker):
def test_requester(mocker):
mocker.patch('jenkins_epo.jenkins.Requester.get_url')
+ mocker.patch('jenkins_epo.jenkins.Requester.post_url')
from jenkins_epo.jenkins import VerboseRequester
VerboseRequester().get_url('url://')
+ VerboseRequester().post_url('url://')
@pytest.mark.asyncio
@asyncio.coroutine
def test_fetch_builds(mocker):
- RESTClient = mocker.patch('jenkins_epo.jenkins.RESTClient')
- RESTClient().aget = aget = CoroutineMock(return_value=dict(builds=[]))
+ Client = mocker.patch('jenkins_epo.jenkins.rest.Client')
+ Client().api.python.aget = aget = CoroutineMock(
+ return_value=dict(builds=[])
+ )
from jenkins_epo.jenkins import Job
api_instance = Mock(_data=dict())
@@ -353,11 +357,13 @@ def test_get_job(factory, load, SETTINGS):
def test_aget_job(mocker, SETTINGS):
mocker.patch('jenkins_epo.jenkins.LazyJenkins.load')
mocker.patch('jenkins_epo.jenkins.Job.factory')
- RESTClient = mocker.patch('jenkins_epo.jenkins.RESTClient')
- client = RESTClient.return_value
- client.return_value.aget = CoroutineMock()
+ Client = mocker.patch('jenkins_epo.jenkins.rest.Client')
+ client = Client()
+ client.api.python.aget = CoroutineMock()
+ client().aget = CoroutineMock()
from jenkins_epo.jenkins import LazyJenkins
+
my = LazyJenkins()
my._instance = Mock()
job = yield from my.aget_job('name')
@@ -395,11 +401,13 @@ def test_queue_empty(mocker, SETTINGS):
JENKINS = LazyJenkins(Mock())
JENKINS.rest = Mock()
- JENKINS.rest.queue.aget = CoroutineMock(return_value=dict(items=[]))
+ JENKINS.rest.queue.api.python.aget = CoroutineMock(
+ return_value=dict(items=[]),
+ )
yield from JENKINS.is_queue_empty()
- assert JENKINS.rest.queue.aget.mock_calls
+ assert JENKINS.rest.queue.api.python.aget.mock_calls
@patch('jenkins_epo.jenkins.JobSpec')
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 8c57441e..8900d373 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -1,21 +1,22 @@
import asyncio
-from asynctest import CoroutineMock, Mock
+from asynctest import CoroutineMock, Mock
import pytest
@pytest.mark.asyncio
@asyncio.coroutine
def test_get(mocker):
- ClientSession = mocker.patch('jenkins_epo.jenkins.aiohttp.ClientSession')
- from jenkins_epo.jenkins import RESTClient
+ ClientSession = mocker.patch('jenkins_epo.rest.aiohttp.ClientSession')
+ from jenkins_epo.rest import Client
- client = RESTClient()
+ client = Client()
client = client('http://jenkins/path').subpath
session = ClientSession.return_value
response = Mock(name='response')
+ response.content_type = 'text/x-python'
session.get = CoroutineMock(return_value=response)
response.read = CoroutineMock(
return_value=repr(dict(unittest=True)).encode('utf-8')
@@ -29,10 +30,10 @@ def test_get(mocker):
@pytest.mark.asyncio
@asyncio.coroutine
def test_post(mocker):
- ClientSession = mocker.patch('jenkins_epo.jenkins.aiohttp.ClientSession')
- from jenkins_epo.jenkins import RESTClient
+ ClientSession = mocker.patch('jenkins_epo.rest.aiohttp.ClientSession')
+ from jenkins_epo.rest import Client
- client = RESTClient()
+ client = Client()
client = client('http://jenkins/path').subpath
session = ClientSession.return_value
@@ -46,3 +47,14 @@ def test_post(mocker):
payload = yield from client.apost(param=1)
assert ': True' in payload
+
+
+def test_payload():
+ from jenkins_epo.rest import Payload
+
+ Payload.factory(Mock(), Mock(), 'string')
+ Payload.factory(Mock(), Mock(), list())
+ Payload.factory(Mock(), Mock(), dict())
+
+ with pytest.raises(Exception):
+ Payload.factory(Mock(), Mock(), object())