Add basic CLI app and client
Change-Id: Ib4a55bf91e16aad2e56fb358a1b23f0b66237953
This commit is contained in:
@@ -7,3 +7,5 @@ flask
|
||||
flask-sqlalchemy
|
||||
flask-restful
|
||||
alembic
|
||||
cliff
|
||||
requests
|
||||
|
||||
@@ -14,3 +14,4 @@ oslotest>=1.10.0 # Apache-2.0
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
||||
requests-mock
|
||||
|
||||
27
tuning_box/cli/__init__.py
Normal file
27
tuning_box/cli/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Licensed 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 cliff import app
|
||||
from cliff import commandmanager
|
||||
|
||||
import tuning_box
|
||||
|
||||
|
||||
class TuningBoxApp(app.App):
|
||||
def __init__(self, client, **kwargs):
|
||||
super(TuningBoxApp, self).__init__(
|
||||
description='Tuning Box - configuration storage for your cloud',
|
||||
version=tuning_box.__version__,
|
||||
command_manager=commandmanager.CommandManager('tuning_box.cli'),
|
||||
**kwargs
|
||||
)
|
||||
self.client = client
|
||||
43
tuning_box/client.py
Normal file
43
tuning_box/client.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Licensed 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.
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
def __init__(self, base_url):
|
||||
self.base_url = base_url
|
||||
self.session = self.get_session()
|
||||
|
||||
def get_session(self):
|
||||
session = requests.Session()
|
||||
session.headers.update(self.default_headers())
|
||||
return session
|
||||
|
||||
def default_headers(self):
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
full_url = self.base_url + url
|
||||
resp = self.session.request(method, full_url, **kwargs)
|
||||
resp.raise_for_status()
|
||||
if resp.headers.get('Content-Type') == 'application/json' and \
|
||||
resp.content:
|
||||
return resp.json()
|
||||
else:
|
||||
return None
|
||||
|
||||
def get(self, url, params=None):
|
||||
return self.request('GET', url, params=params)
|
||||
69
tuning_box/tests/test_cli.py
Normal file
69
tuning_box/tests/test_cli.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# Licensed 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.
|
||||
|
||||
import io
|
||||
|
||||
from requests_mock.contrib import fixture as req_fixture
|
||||
|
||||
from tuning_box import cli
|
||||
from tuning_box import client as tb_client
|
||||
from tuning_box.tests import base
|
||||
|
||||
|
||||
class SafeTuningBoxApp(cli.TuningBoxApp):
|
||||
def __init__(self, client):
|
||||
super(SafeTuningBoxApp, self).__init__(
|
||||
client=client,
|
||||
**self.get_std_streams()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_std_streams():
|
||||
if bytes is str:
|
||||
io_cls = io.BytesIO
|
||||
else:
|
||||
io_cls = io.StringIO
|
||||
return {k: io_cls() for k in ('stdin', 'stdout', 'stderr')}
|
||||
|
||||
def build_option_parser(self, description, version, argparse_kwargs=None):
|
||||
parser = super(SafeTuningBoxApp, self).build_option_parser(
|
||||
description, version, argparse_kwargs)
|
||||
parser.set_defaults(debug=True)
|
||||
return parser
|
||||
|
||||
def get_fuzzy_matches(self, cmd):
|
||||
# Turn off guessing, we need exact failures in tests
|
||||
return []
|
||||
|
||||
def run(self, argv):
|
||||
try:
|
||||
exit_code = super(SafeTuningBoxApp, self).run(argv)
|
||||
except SystemExit as e:
|
||||
exit_code = e.code
|
||||
assert exit_code == 0
|
||||
|
||||
|
||||
class _BaseCLITest(base.TestCase):
|
||||
BASE_URL = 'http://somehost/prefix'
|
||||
|
||||
def setUp(self):
|
||||
super(_BaseCLITest, self).setUp()
|
||||
client = tb_client.HTTPClient(self.BASE_URL)
|
||||
self.req_mock = self.useFixture(req_fixture.Fixture())
|
||||
self.cli = SafeTuningBoxApp(client=client)
|
||||
|
||||
|
||||
class TestApp(_BaseCLITest):
|
||||
def test_help(self):
|
||||
self.cli.run(["--help"])
|
||||
self.assertEqual('', self.cli.stderr.getvalue())
|
||||
self.assertNotIn('Could not', self.cli.stdout.getvalue())
|
||||
Reference in New Issue
Block a user