Add support for using Falcon 2.0.0

Falcon 2.0.0 introduces some breaking changes. The relevant ones here are:

- falcon.testing.TestCase.api property was removed
- falcon.testing.TestBase class was removed
- falcon.HTTPRequestEntityTooLarge exception was renamed to
 falcon.HTTPPayloadTooLarge

Additionally, the default behaviour for handling trailing slashes on
URIs also changed:

https://falcon.readthedocs.io/en/latest/user/faq.html#how-does-falcon-
handle-a-trailing-slash-in-the-request-path

This commit adds support for using the new release.

Story: 2005695
Task: 33474

Change-Id: I2379522efef81b919098a0143b66cc259a184e70
This commit is contained in:
Adrian Czarnecki 2019-05-23 11:44:08 +02:00
parent 21736bb927
commit 29e25e6e29
15 changed files with 131 additions and 139 deletions

View File

@ -12,7 +12,7 @@ docutils==0.11
dulwich==0.15.0
eventlet==0.18.2
extras==1.0.0
falcon==1.0.0
falcon==2.0.0
fixtures==3.0.0
flake8==2.5.5
future==0.16.0

View File

@ -77,6 +77,7 @@ def create_version_app(global_conf, **local_conf):
wsgi_app = falcon.API(
request_type=request.Request
)
wsgi_app.req_options.strip_url_path_trailing_slash = True
for route, ctrl in controllers.items():
wsgi_app.add_route(route, ctrl)
return wsgi_app

View File

@ -207,7 +207,7 @@ def validate_payload_size(req):
)
if payload_size >= max_size:
raise falcon.HTTPRequestEntityTooLarge(
raise falcon.HTTPPayloadTooLarge(
title='Log payload size exceeded',
description='Maximum allowed size is %d bytes' % max_size
)

View File

@ -15,7 +15,6 @@
import datetime
from monasca_common.rest import utils as rest_utils
from oslo_config import cfg
from oslo_log import log
@ -87,7 +86,6 @@ class LogCreator(object):
:keyword: log_object
"""
payload = rest_utils.read_body(payload, content_type)
if not payload:
return None

View File

@ -92,7 +92,7 @@ class Logs(logs_api.LogsApi):
return self._log_creator.new_log(
application_type=request.get_header(*headers.X_APPLICATION_TYPE),
dimensions=request.get_header(*headers.X_DIMENSIONS),
payload=request.stream,
payload=request.media,
content_type=request.content_type
)

View File

@ -33,9 +33,13 @@ def read_json_msg_body(req):
:raises falcon.HTTPBadRequest:
"""
try:
msg = req.stream.read()
json_msg = rest_utils.from_json(msg)
return json_msg
body = req.media
if body is not None:
return body
else:
raise falcon.HTTPBadRequest('Bad request',
'Request body is Empty')
except rest_utils.exceptions.DataConversionException as ex:
LOG.debug(ex)

View File

@ -39,24 +39,6 @@ from monasca_log_api import policies
policy.POLICIES = policies
class MockedAPI(falcon.API):
"""MockedAPI
Subclasses :py:class:`falcon.API` in order to overwrite
request_type property with custom :py:class:`request.Request`
"""
def __init__(self):
super(MockedAPI, self).__init__(
media_type=falcon.DEFAULT_MEDIA_TYPE,
request_type=request.Request,
response_type=falcon.Response,
middleware=None,
router=None
)
def generate_unique_message(size):
letters = string.ascii_letters
@ -214,5 +196,12 @@ class BaseTestCase(oslotest_base.BaseTestCase):
config.CONF.set_default(k, v, group)
class BaseApiTestCase(BaseTestCase, testing.TestBase):
api_class = MockedAPI
class BaseApiTestCase(BaseTestCase, testing.TestCase):
def setUp(self):
super(BaseApiTestCase, self).setUp()
self.app = falcon.API(request_type=request.Request)
# NOTE(czarneckia): Falcon 2.0.0 switches the default for this from True
# to False so we explicitly set it here to prevent the behaviour
# changing between versions.
self.app.req_options.strip_url_path_trailing_slash = True

View File

@ -14,7 +14,6 @@
import falcon
import mock
import simplejson as json
from monasca_log_api.app.controller import healthchecks
from monasca_log_api.healthcheck import kafka_check as healthcheck
@ -25,16 +24,19 @@ ENDPOINT = '/healthcheck'
class TestApiHealthChecks(base.BaseApiTestCase):
def before(self):
def setUp(self):
super(TestApiHealthChecks, self).setUp()
self.resource = healthchecks.HealthChecks()
self.api.add_route(
self.app.add_route(
ENDPOINT,
self.resource
)
def test_should_return_200_for_head(self):
self.simulate_request(ENDPOINT, method='HEAD')
self.assertEqual(falcon.HTTP_NO_CONTENT, self.srmock.status)
res = self.simulate_request(
path=ENDPOINT,
method='HEAD')
self.assertEqual(falcon.HTTP_NO_CONTENT, res.status)
@mock.patch('monasca_log_api.healthcheck.kafka_check.KafkaHealthCheck')
def test_should_report_healthy_if_kafka_healthy(self, kafka_check):
@ -42,17 +44,16 @@ class TestApiHealthChecks(base.BaseApiTestCase):
'OK')
self.resource._kafka_check = kafka_check
ret = self.simulate_request(ENDPOINT,
res = self.simulate_request(path=ENDPOINT,
headers={
'Content-Type': 'application/json'
},
decode='utf8',
method='GET')
self.assertEqual(falcon.HTTP_OK, self.srmock.status)
self.assertEqual(falcon.HTTP_OK, res.status)
ret = json.loads(ret)
self.assertIn('kafka', ret)
self.assertEqual('OK', ret.get('kafka'))
res = res.json
self.assertIn('kafka', res)
self.assertEqual('OK', res.get('kafka'))
@mock.patch('monasca_log_api.healthcheck.kafka_check.KafkaHealthCheck')
def test_should_report_unhealthy_if_kafka_healthy(self, kafka_check):
@ -62,14 +63,13 @@ class TestApiHealthChecks(base.BaseApiTestCase):
err_str)
self.resource._kafka_check = kafka_check
ret = self.simulate_request(ENDPOINT,
res = self.simulate_request(path=ENDPOINT,
headers={
'Content-Type': 'application/json'
},
decode='utf8',
method='GET')
self.assertEqual(falcon.HTTP_SERVICE_UNAVAILABLE, self.srmock.status)
self.assertEqual(falcon.HTTP_SERVICE_UNAVAILABLE, res.status)
ret = json.loads(ret)
self.assertIn('kafka', ret)
self.assertEqual(err_str, ret.get('kafka'))
res = res.json
self.assertIn('kafka', res)
self.assertEqual(err_str, res.get('kafka'))

View File

@ -15,6 +15,7 @@
import falcon
import mock
import ujson
from monasca_log_api.app.base import exceptions as log_api_exceptions
from monasca_log_api.app.controller.api import headers
@ -26,7 +27,7 @@ ROLES = 'admin'
def _init_resource(test):
resource = logs.Logs()
test.api.add_route('/log/single', resource)
test.app.add_route('/log/single', resource)
return resource
@ -47,8 +48,8 @@ class TestApiLogs(base.BaseApiTestCase):
__):
_init_resource(self)
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -58,16 +59,16 @@ class TestApiLogs(base.BaseApiTestCase):
}
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertIn('deprecated', self.srmock.headers_dict)
self.assertIn('link', self.srmock.headers_dict)
self.assertEqual(falcon.HTTP_204, res.status)
self.assertIn('deprecated', res.headers)
self.assertIn('link', res.headers)
@mock.patch('monasca_log_api.app.base.log_publisher.LogPublisher')
@mock.patch('monasca_log_api.app.controller.v2.aid.service.LogCreator')
def test_should_fail_not_delegate_ok_cross_tenant_id(self, _, __):
_init_resource(self)
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
query_string='tenant_id=1',
headers={
@ -75,7 +76,7 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Length': '0'
}
)
self.assertEqual(falcon.HTTP_401, self.srmock.status)
self.assertEqual(falcon.HTTP_401, res.status)
@mock.patch('monasca_log_api.app.controller.v2.aid.service.LogCreator')
@mock.patch('monasca_log_api.app.base.log_publisher.LogPublisher')
@ -86,8 +87,8 @@ class TestApiLogs(base.BaseApiTestCase):
logs_resource._log_creator = log_creator
logs_resource._kafka_publisher = kafka_publisher
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -96,7 +97,7 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Length': '0'
}
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual(falcon.HTTP_204, res.status)
self.assertEqual(1, kafka_publisher.send_message.call_count)
self.assertEqual(1, log_creator.new_log.call_count)
@ -111,8 +112,8 @@ class TestApiLogs(base.BaseApiTestCase):
logs_resource._log_creator = log_creator
logs_resource._kafka_publisher = kafka_publisher
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -121,7 +122,7 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Length': '0'
}
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual(falcon.HTTP_204, res.status)
self.assertEqual(1, kafka_publisher.send_message.call_count)
self.assertEqual(1, log_creator.new_log.call_count)
@ -136,8 +137,8 @@ class TestApiLogs(base.BaseApiTestCase):
resource._log_creator = log_creator
resource._kafka_publisher = log_publisher
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
query_string='tenant_id=1',
headers={
@ -147,7 +148,7 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Length': '0'
}
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual(falcon.HTTP_204, res.status)
self.assertEqual(1, log_publisher.send_message.call_count)
self.assertEqual(1, log_creator.new_log.call_count)
@ -159,26 +160,25 @@ class TestApiLogs(base.BaseApiTestCase):
_init_resource(self)
rest_utils.read_body.return_value = True
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
headers.X_DIMENSIONS.name: '',
'Content-Type': 'application/json',
'Content-Length': '0'
},
body='{"message":"test"}'
)
self.assertEqual(log_api_exceptions.HTTP_422, self.srmock.status)
self.assertEqual(log_api_exceptions.HTTP_422, res.status)
@mock.patch('monasca_log_api.app.controller.v2.aid.service.LogCreator')
@mock.patch('monasca_log_api.app.base.log_publisher.LogPublisher')
def test_should_fail_for_invalid_content_type(self, _, __):
_init_resource(self)
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -187,7 +187,7 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Length': '0'
}
)
self.assertEqual(falcon.HTTP_415, self.srmock.status)
self.assertEqual(falcon.HTTP_415, res.status)
@mock.patch('monasca_log_api.app.controller.v2.aid.service.LogCreator')
@mock.patch('monasca_log_api.app.base.log_publisher.LogPublisher')
@ -195,20 +195,25 @@ class TestApiLogs(base.BaseApiTestCase):
_init_resource(self)
max_log_size = 1000
content_length = max_log_size - 100
body = ujson.dumps({
'message': 't' * (max_log_size - 100)
})
content_length = len(body)
self.conf_override(max_log_size=max_log_size, group='service')
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
headers.X_DIMENSIONS.name: '',
'Content-Type': 'application/json',
'Content-Length': str(content_length)
}
},
body=body
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual(falcon.HTTP_204, res.status)
@mock.patch('monasca_log_api.app.controller.v2.aid.service.LogCreator')
@mock.patch('monasca_log_api.app.base.log_publisher.LogPublisher')
@ -219,8 +224,8 @@ class TestApiLogs(base.BaseApiTestCase):
content_length = max_log_size + 100
self.conf_override(max_log_size=max_log_size, group='service')
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -229,7 +234,7 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Length': str(content_length)
}
)
self.assertEqual(falcon.HTTP_413, self.srmock.status)
self.assertEqual(falcon.HTTP_413, res.status)
@mock.patch('monasca_log_api.app.controller.v2.aid.service.LogCreator')
@mock.patch('monasca_log_api.app.base.log_publisher.LogPublisher')
@ -240,8 +245,8 @@ class TestApiLogs(base.BaseApiTestCase):
content_length = max_log_size
self.conf_override(max_log_size=max_log_size, group='service')
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -250,15 +255,15 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Length': str(content_length)
}
)
self.assertEqual(falcon.HTTP_413, self.srmock.status)
self.assertEqual(falcon.HTTP_413, res.status)
@mock.patch('monasca_log_api.app.controller.v2.aid.service.LogCreator')
@mock.patch('monasca_log_api.app.base.log_publisher.LogPublisher')
def test_should_fail_content_length(self, _, __):
_init_resource(self)
self.simulate_request(
'/log/single',
res = self.simulate_request(
path='/log/single',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -266,4 +271,4 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Type': 'application/json'
}
)
self.assertEqual(falcon.HTTP_411, self.srmock.status)
self.assertEqual(falcon.HTTP_411, res.status)

View File

@ -14,6 +14,7 @@
import falcon
import mock
from six import PY3
import ujson as json
from monasca_log_api.app.base import exceptions as log_api_exceptions
@ -28,7 +29,7 @@ ROLES = 'admin'
def _init_resource(test):
resource = logs.Logs()
test.api.add_route(ENDPOINT, resource)
test.app.add_route(ENDPOINT, resource)
return resource
@ -92,7 +93,7 @@ class TestApiLogsMonitoring(base.BaseApiTestCase):
content_length = len(payload)
self.simulate_request(
ENDPOINT,
path=ENDPOINT,
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -128,7 +129,7 @@ class TestApiLogsMonitoring(base.BaseApiTestCase):
res._processor._get_dimensions = mock.Mock(side_effect=side_effects)
self.simulate_request(
ENDPOINT,
path=ENDPOINT,
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -172,7 +173,7 @@ class TestApiLogsMonitoring(base.BaseApiTestCase):
payload = json.dumps(v3_body)
content_length = len(payload)
self.simulate_request(
ENDPOINT,
path=ENDPOINT,
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -211,8 +212,8 @@ class TestApiLogs(base.BaseApiTestCase):
v3_body, v3_logs = _generate_v3_payload(1)
payload = json.dumps(v3_body)
content_length = len(payload)
self.simulate_request(
'/logs',
res = self.simulate_request(
path='/logs',
method='POST',
query_string='tenant_id=1',
headers={
@ -222,7 +223,7 @@ class TestApiLogs(base.BaseApiTestCase):
},
body=payload
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual(falcon.HTTP_204, res.status)
logs_resource._processor.send_message.assert_called_with(
logs=v3_logs,
global_dimensions=v3_body['dimensions'],
@ -232,8 +233,8 @@ class TestApiLogs(base.BaseApiTestCase):
'BulkProcessor')
def test_should_fail_not_delegate_ok_cross_tenant_id(self, _):
_init_resource(self)
self.simulate_request(
'/logs',
res = self.simulate_request(
path='/logs',
method='POST',
query_string='tenant_id=1',
headers={
@ -242,7 +243,7 @@ class TestApiLogs(base.BaseApiTestCase):
'Content-Length': '0'
}
)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.assertEqual(falcon.HTTP_400, res.status)
@mock.patch('monasca_log_api.app.controller.v3.aid.bulk_processor.'
'BulkProcessor')
@ -254,8 +255,8 @@ class TestApiLogs(base.BaseApiTestCase):
v3_body, _ = _generate_v3_payload(1)
payload = json.dumps(v3_body)
content_length = len(payload)
self.simulate_request(
'/logs',
res = self.simulate_request(
path='/logs',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -264,7 +265,7 @@ class TestApiLogs(base.BaseApiTestCase):
},
body=payload
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual(falcon.HTTP_204, res.status)
self.assertEqual(1, bulk_processor.send_message.call_count)
@mock.patch('monasca_log_api.app.controller.v3.aid.bulk_processor.'
@ -277,8 +278,8 @@ class TestApiLogs(base.BaseApiTestCase):
v3_body, _ = _generate_v3_payload(1)
payload = json.dumps(v3_body)
content_length = len(payload)
self.simulate_request(
'/logs',
res = self.simulate_request(
path='/logs',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -287,7 +288,7 @@ class TestApiLogs(base.BaseApiTestCase):
},
body=payload
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual(falcon.HTTP_204, res.status)
self.assertEqual(1, bulk_processor.send_message.call_count)
@ -301,9 +302,9 @@ class TestUnicodeLogs(base.BaseApiTestCase):
messages = [m['input'] for m in base.UNICODE_MESSAGES]
v3_body, _ = _generate_v3_payload(messages=messages)
payload = json.dumps(v3_body, ensure_ascii=False)
content_length = len(payload)
self.simulate_request(
'/logs',
content_length = len(payload.encode('utf8') if PY3 else payload)
res = self.simulate_request(
path='/logs',
method='POST',
headers={
headers.X_ROLES.name: ROLES,
@ -312,4 +313,4 @@ class TestUnicodeLogs(base.BaseApiTestCase):
},
body=payload
)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual(falcon.HTTP_204, res.status)

View File

@ -304,7 +304,7 @@ class PayloadSizeValidations(base.BaseTestCase):
req.content_length = content_length
self.assertRaises(
errors.HTTPRequestEntityTooLarge,
errors.HTTPPayloadTooLarge,
validation.validate_payload_size,
req
)
@ -319,7 +319,7 @@ class PayloadSizeValidations(base.BaseTestCase):
req.content_length = content_length
self.assertRaises(
errors.HTTPRequestEntityTooLarge,
errors.HTTPPayloadTooLarge,
validation.validate_payload_size,
req
)
@ -357,14 +357,13 @@ class LogsCreatorNewLog(base.BaseTestCase):
super(LogsCreatorNewLog, self).setUp()
self.instance = aid_service.LogCreator()
@mock.patch('io.IOBase')
def test_should_create_log_from_json(self, payload):
def test_should_create_log_from_json(self):
msg = u'Hello World'
path = u'/var/log/messages'
json_msg = u'{"path":"%s","message":"%s"}' % (path, msg)
payload = {"path": path,
"message": msg}
app_type = 'monasca'
dimensions = 'cpu_time:30'
payload.read.return_value = json_msg
expected_log = {
'message': msg,
@ -381,14 +380,12 @@ class LogsCreatorNewLog(base.BaseTestCase):
payload=payload
))
@mock.patch('io.IOBase')
def test_should_create_log_from_text(self, payload):
def test_should_create_log_from_text(self):
msg = u'Hello World'
app_type = 'monasca'
dimension_name = 'cpu_time'
dimension_value = 30
dimensions = '%s:%s' % (dimension_name, str(dimension_value))
payload.read.return_value = msg
expected_log = {
'message': msg,
@ -401,7 +398,7 @@ class LogsCreatorNewLog(base.BaseTestCase):
self.assertEqual(expected_log, self.instance.new_log(
application_type=app_type,
dimensions=dimensions,
payload=payload,
payload=msg,
content_type='text/plain'
))

View File

@ -66,11 +66,11 @@ class TestApiSameV2V3Output(base.BaseApiTestCase):
]
}
self.api.add_route('/v2.0', v2)
self.api.add_route('/v3.0', v3)
self.app.add_route('/v2.0', v2)
self.app.add_route('/v3.0', v3)
self.simulate_request(
'/v2.0',
path='/v2.0',
method='POST',
headers={
headers.X_ROLES.name: roles,
@ -78,19 +78,17 @@ class TestApiSameV2V3Output(base.BaseApiTestCase):
headers.X_APPLICATION_TYPE.name: component,
headers.X_TENANT_ID.name: tenant_id,
'Content-Type': 'application/json',
'Content-Length': '100'
},
body=json.dumps(v2_body)
)
self.simulate_request(
'/v3.0',
path='/v3.0',
method='POST',
headers={
headers.X_ROLES.name: roles,
headers.X_TENANT_ID.name: tenant_id,
'Content-Type': 'application/json',
'Content-Length': '100'
},
body=json.dumps(v3_body)
)

View File

@ -13,7 +13,6 @@
# under the License.
import falcon
import ujson as json
from monasca_log_api.app.controller import versions
from monasca_log_api.tests import base
@ -25,24 +24,25 @@ def _get_versioned_url(version_id):
class TestApiVersions(base.BaseApiTestCase):
def before(self):
def setUp(self):
super(TestApiVersions, self).setUp()
self.versions = versions.Versions()
self.api.add_route("/version/", self.versions)
self.api.add_route("/version/{version_id}", self.versions)
self.app.add_route("/version/", self.versions)
self.app.add_route("/version/{version_id}", self.versions)
def test_should_fail_for_unsupported_version(self):
unsupported_version = 'v5.0'
uri = _get_versioned_url(unsupported_version)
self.simulate_request(
uri,
res = self.simulate_request(
path=uri,
method='GET',
headers={
'Content-Type': 'application/json'
}
)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.assertEqual(falcon.HTTP_400, res.status)
def test_should_return_all_supported_versions(self):
@ -78,16 +78,15 @@ class TestApiVersions(base.BaseApiTestCase):
expected_links_keys = 'self', 'version', 'healthcheck'
res = self.simulate_request(
'/version',
path='/version',
method='GET',
headers={
'Content-Type': 'application/json'
},
decode='utf-8'
}
)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self.assertEqual(falcon.HTTP_200, res.status)
response = json.loads(res)
response = res.json
_check_elements()
_check_global_links()
@ -97,16 +96,15 @@ class TestApiVersions(base.BaseApiTestCase):
for expected_version in expected_versions:
uri = _get_versioned_url(expected_version)
res = self.simulate_request(
uri,
path=uri,
method='GET',
headers={
'Content-Type': 'application/json'
},
decode='utf-8'
)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self.assertEqual(falcon.HTTP_200, res.status)
response = json.loads(res)
response = res.json
self.assertIn('elements', response)
self.assertIn('links', response)

View File

@ -4,7 +4,7 @@
pbr!=2.1.0,>=2.0.0 # Apache-2.0
Paste>=2.0.2 # MIT
falcon>=1.0.0 # Apache-2.0
falcon>=2.0.0 # Apache-2.0
keystonemiddleware>=4.17.0 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0
oslo.context>=2.19.2 # Apache-2.0

View File

@ -4,7 +4,7 @@
# Install bounded pep8/pyflakes first, then let flake8 install
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
bandit>=1.1.0 # Apache-2.0
bandit!=1.6.0,>=1.1.0 # Apache-2.0
bashate>=0.5.1 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
@ -16,7 +16,8 @@ simplejson>=3.5.1 # MIT
# documentation
doc8>=0.6.0 # Apache-2.0
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD
sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD
os-api-ref>=1.4.0 # Apache-2.0
reno>=2.5.0 # Apache-2.0
openstackdocstheme>=1.18.1 # Apache-2.0