Merge pull request #24 from dod-ccpo/api-client

Add a new ApiClient for making async requests to other services
This commit is contained in:
richard-dds
2018-06-11 16:48:22 -04:00
committed by GitHub
13 changed files with 285 additions and 31 deletions

2
.gitignore vendored
View File

@@ -25,3 +25,5 @@ static/fonts/*
# local log files # local log files
log/* log/*
config/dev.ini

View File

@@ -7,10 +7,13 @@ name = "pypi"
tornado = "==5.0.2" tornado = "==5.0.2"
webassets = "==0.12.1" webassets = "==0.12.1"
Unipath = "==1.1" Unipath = "==1.1"
requests = "*"
[dev-packages] [dev-packages]
pytest = "==3.6.0" pytest = "==3.6.0"
pytest-tornado = "==0.5.0" pytest-tornado = "==0.5.0"
ipython = "*"
ipdb = "*"
[requires] [requires]
python_version = "3.6" python_version = "3.6"

153
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "8d3125836797aa0d47e617fde767493efeb5d912980e2a0ba7f5740fbab8c35c" "sha256": "68a0d5093979093899f0f86faa82eb55f90f9a67a16b11a5701ea85096e72ee8"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -16,6 +16,35 @@
] ]
}, },
"default": { "default": {
"certifi": {
"hashes": [
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
],
"version": "==2018.4.16"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
],
"version": "==2.6"
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
],
"index": "pypi",
"version": "==2.18.4"
},
"tornado": { "tornado": {
"hashes": [ "hashes": [
"sha256:1b83d5c10550f2653380b4c77331d6f8850f287c4f67d7ce1e1c639d9222fbc7", "sha256:1b83d5c10550f2653380b4c77331d6f8850f287c4f67d7ce1e1c639d9222fbc7",
@@ -35,6 +64,13 @@
"index": "pypi", "index": "pypi",
"version": "==1.1" "version": "==1.1"
}, },
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
],
"version": "==1.22"
},
"webassets": { "webassets": {
"hashes": [ "hashes": [
"sha256:e7d9c8887343123fd5b32309b33167428cb1318cdda97ece12d0907fd69d38db" "sha256:e7d9c8887343123fd5b32309b33167428cb1318cdda97ece12d0907fd69d38db"
@@ -44,6 +80,14 @@
} }
}, },
"develop": { "develop": {
"appnope": {
"hashes": [
"sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0",
"sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"
],
"markers": "sys_platform == 'darwin'",
"version": "==0.1.0"
},
"atomicwrites": { "atomicwrites": {
"hashes": [ "hashes": [
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
@@ -58,6 +102,49 @@
], ],
"version": "==18.1.0" "version": "==18.1.0"
}, },
"backcall": {
"hashes": [
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
"sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"
],
"version": "==0.1.0"
},
"decorator": {
"hashes": [
"sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82",
"sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c"
],
"version": "==4.3.0"
},
"ipdb": {
"hashes": [
"sha256:7081c65ed7bfe7737f83fa4213ca8afd9617b42ff6b3f1daf9a3419839a2a00a"
],
"index": "pypi",
"version": "==0.11"
},
"ipython": {
"hashes": [
"sha256:a0c96853549b246991046f32d19db7140f5b1a644cc31f0dc1edc86713b7676f",
"sha256:eca537aa61592aca2fef4adea12af8e42f5c335004dfa80c78caf80e8b525e5c"
],
"index": "pypi",
"version": "==6.4.0"
},
"ipython-genutils": {
"hashes": [
"sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
"sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
],
"version": "==0.2.0"
},
"jedi": {
"hashes": [
"sha256:1972f694c6bc66a2fac8718299e2ab73011d653a6d8059790c3476d2353b99ad",
"sha256:5861f6dc0c16e024cbb0044999f9cf8013b292c05f287df06d3d991a87a4eb89"
],
"version": "==0.12.0"
},
"more-itertools": { "more-itertools": {
"hashes": [ "hashes": [
"sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8", "sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
@@ -66,6 +153,28 @@
], ],
"version": "==4.2.0" "version": "==4.2.0"
}, },
"parso": {
"hashes": [
"sha256:cdef26e8adc10d589f3ec4eb444bd0a29f3f1eb6d72a4292ab8afcb9d68976a6",
"sha256:f0604a40b96e062b0fd99cf134cc2d5cdf66939d0902f8267d938b0d5b26707f"
],
"version": "==0.2.1"
},
"pexpect": {
"hashes": [
"sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba",
"sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b"
],
"markers": "sys_platform != 'win32'",
"version": "==4.6.0"
},
"pickleshare": {
"hashes": [
"sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b",
"sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5"
],
"version": "==0.7.4"
},
"pluggy": { "pluggy": {
"hashes": [ "hashes": [
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
@@ -74,6 +183,21 @@
], ],
"version": "==0.6.0" "version": "==0.6.0"
}, },
"prompt-toolkit": {
"hashes": [
"sha256:1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381",
"sha256:3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4",
"sha256:858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917"
],
"version": "==1.0.15"
},
"ptyprocess": {
"hashes": [
"sha256:e64193f0047ad603b71f202332ab5527c5e52aa7c8b609704fc28c0dc20c4365",
"sha256:e8c43b5eee76b2083a9badde89fd1bbce6c8942d1045146e100b7b5e014f4f1a"
],
"version": "==0.5.2"
},
"py": { "py": {
"hashes": [ "hashes": [
"sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881",
@@ -81,6 +205,13 @@
], ],
"version": "==1.5.3" "version": "==1.5.3"
}, },
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"version": "==2.2.0"
},
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:39555d023af3200d004d09e51b4dd9fdd828baa863cded3fd6ba2f29f757ae2d", "sha256:39555d023af3200d004d09e51b4dd9fdd828baa863cded3fd6ba2f29f757ae2d",
@@ -97,6 +228,12 @@
"index": "pypi", "index": "pypi",
"version": "==0.5.0" "version": "==0.5.0"
}, },
"simplegeneric": {
"hashes": [
"sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"
],
"version": "==0.8.1"
},
"six": { "six": {
"hashes": [ "hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
@@ -114,6 +251,20 @@
], ],
"index": "pypi", "index": "pypi",
"version": "==5.0.2" "version": "==5.0.2"
},
"traitlets": {
"hashes": [
"sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835",
"sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"
],
"version": "==4.3.2"
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
],
"version": "==0.1.7"
} }
} }
} }

12
app.py
View File

@@ -1,11 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
from atst.app import make_app
import tornado.ioloop
import os import os
import tornado.ioloop
app = make_app(debug=os.getenv('DEBUG',False)) from atst.app import make_app, make_config
port = 8888
config = make_config()
app = make_app(config)
if __name__ == '__main__':
port = int(config['default']['PORT'])
app.listen(port) app.listen(port)
print("Listening on http://localhost:%i" % port) print("Listening on http://localhost:%i" % port)
tornado.ioloop.IOLoop.current().start() tornado.ioloop.IOLoop.current().start()

46
atst/api_client.py Normal file
View File

@@ -0,0 +1,46 @@
import tornado.gen
from tornado.httpclient import AsyncHTTPClient
from json import dumps, loads
class ApiClient(object):
def __init__(self, base_url):
self.base_url = base_url
self.client = AsyncHTTPClient()
@tornado.gen.coroutine
def get(self, path, **kwargs):
return (yield self.make_request('GET', self.base_url + path, **kwargs))
@tornado.gen.coroutine
def put(self, path, **kwargs):
return (yield self.make_request('PUT', self.base_url + path, **kwargs))
@tornado.gen.coroutine
def post(self, path, **kwargs):
return (yield self.make_request('POST', self.base_url + path, **kwargs))
@tornado.gen.coroutine
def delete(self, path, **kwargs):
return (yield self.make_request('DELETE', self.base_url + path, **kwargs))
@tornado.gen.coroutine
def make_request(self, method, url, **kwargs):
# If 'json' kwarg is specified, serialize it to 'body' and update
# the Content-Type.
if 'json' in kwargs:
kwargs['body'] = dumps(kwargs['json'])
del kwargs['json']
headers = kwargs.get('headers', {})
headers['Content-Type'] = 'application-json'
kwargs['headers'] = headers
response = yield self.client.fetch(url, method=method, **kwargs)
return self.adapt_response(response)
def adapt_response(self, response):
if response.headers['Content-Type'] == 'application/json':
json = loads(response.body)
setattr(response, 'json', json)
return response

View File

@@ -1,16 +1,27 @@
import os
from configparser import ConfigParser
import tornado.web import tornado.web
from tornado.web import url
from atst.handlers.main import MainHandler from atst.handlers.main import MainHandler
from atst.handlers.workspace import Workspace from atst.handlers.workspace import Workspace
from atst.handlers.request import Request from atst.handlers.request import Request
from atst.handlers.request_new import RequestNew from atst.handlers.request_new import RequestNew
from atst.home import home from atst.home import home
from tornado.web import url from atst.api_client import ApiClient
def make_app(config):
authz_client = ApiClient(config['default']['AUTHZ_BASE_URL'])
def make_app(**kwargs):
app = tornado.web.Application([ app = tornado.web.Application([
url( r"/", MainHandler, {'page': 'login'}, name='login' ), url( r"/", MainHandler, {'page': 'login'}, name='login' ),
url( r"/home", MainHandler, {'page': 'home'}, name='home' ), url( r"/home", MainHandler, {'page': 'home'}, name='home' ),
url( r"/workspaces", Workspace, {'page': 'workspaces'}, name='workspaces' ), url( r"/workspaces",
Workspace,
{'page': 'workspaces', 'authz_client': authz_client},
name='workspaces'),
url( r"/requests", Request, {'page': 'requests'}, name='requests' ), url( r"/requests", Request, {'page': 'requests'}, name='requests' ),
url( r"/requests/new", RequestNew, {'page': 'requests_new'}, name='request_new' ), url( r"/requests/new", RequestNew, {'page': 'requests_new'}, name='request_new' ),
url( r"/requests/new/([0-9])", RequestNew, {'page': 'requests_new'}, name='request_form' ), url( r"/requests/new/([0-9])", RequestNew, {'page': 'requests_new'}, name='request_form' ),
@@ -20,6 +31,23 @@ def make_app(**kwargs):
], ],
template_path = home.child('templates'), template_path = home.child('templates'),
static_path = home.child('static'), static_path = home.child('static'),
**kwargs DEBUG=config['default']['DEBUG']
) )
return app return app
def make_config():
BASE_CONFIG_FILENAME = os.path.join(
os.path.dirname(__file__),
'../config/base.ini',
)
ENV_CONFIG_FILENAME = os.path.join(
os.path.dirname(__file__),
'../config/',
'{}.ini'.format(os.getenv('TORNADO_ENV', 'dev').lower())
)
config = ConfigParser()
# ENV_CONFIG will override values in BASE_CONFIG.
config.read([BASE_CONFIG_FILENAME, ENV_CONFIG_FILENAME])
return config

View File

@@ -1,8 +1,11 @@
from atst.handler import BaseHandler from atst.handler import BaseHandler
import requests
import tornado.gen
mock_workspaces = [ mock_workspaces = [
{ {
'name' : 'Unclassified IaaS and PaaS for Defense Digital Service (DDS)', 'name' : 'Unclassified IaaS and PaaS for Defense Digital Service (DDS)',
'id': '5966187a-eff9-44c3-aa15-4de7a65ac7ff',
'task_order' : { 'task_order' : {
'number' : 123456, 'number' : 123456,
}, },
@@ -10,10 +13,14 @@ mock_workspaces = [
} }
] ]
session = requests.Session()
class Workspace(BaseHandler): class Workspace(BaseHandler):
def initialize(self, page): def initialize(self, page, authz_client):
self.page = page self.page = page
self.authz_client = authz_client
@tornado.gen.coroutine
def get(self): def get(self):
self.render( 'workspaces.html.to', page = self.page, workspaces = mock_workspaces ) self.render( 'workspaces.html.to', page = self.page, workspaces = mock_workspaces )

4
config/base.ini Normal file
View File

@@ -0,0 +1,4 @@
[default]
ENVIRONMENT = dev
DEBUG = true
AUTHZ_BASE_URL = http://localhost

0
config/ci.ini Normal file
View File

9
tests/conftest.py Normal file
View File

@@ -0,0 +1,9 @@
import pytest
from atst.app import make_app, make_config
@pytest.fixture
def app():
config = make_config()
return make_app(config)

10
tests/test_api_client.py Normal file
View File

@@ -0,0 +1,10 @@
import pytest
from atst.api_client import ApiClient
@pytest.mark.gen_test
def test_api_client(http_client, base_url):
client = ApiClient(base_url)
response = yield client.get('')
assert response.code == 200

View File

@@ -1,10 +1,5 @@
import pytest import pytest
import tornado.web
from atst.app import make_app
@pytest.fixture
def app():
return make_app()
@pytest.mark.gen_test @pytest.mark.gen_test
def test_hello_world(http_client, base_url): def test_hello_world(http_client, base_url):

View File

@@ -1,10 +1,5 @@
import pytest import pytest
import tornado.web
from atst.app import make_app
@pytest.fixture
def app():
return make_app()
@pytest.mark.gen_test @pytest.mark.gen_test
def test_routes(http_client, base_url): def test_routes(http_client, base_url):