diff --git a/README.rst b/README.rst index da3c697..04fdec2 100644 --- a/README.rst +++ b/README.rst @@ -11,6 +11,12 @@ A simple parser for OpenStack microversion headers:: headers, service_type='compute', legacy_headers=['x-openstack-nova-api-version']) + # If headers are not already available, a dict of headers + # can be extracted from the WSGI environ + headers = microversion_parse.headers_from_wsgi_environ(environ) + version = microversion_parse.get_version( + headers, service_type='placement') + It processes microversion headers with the standard form:: OpenStack-API-Version: compute 2.1 diff --git a/microversion_parse/__init__.py b/microversion_parse/__init__.py index cb3fe63..6fc8071 100644 --- a/microversion_parse/__init__.py +++ b/microversion_parse/__init__.py @@ -16,6 +16,7 @@ __version__ = '0.1.1' import collections +ENVIRON_HTTP_HEADER_FMT = 'http_{}' STANDARD_HEADER = 'openstack-api-version' @@ -63,7 +64,7 @@ def check_legacy_headers(headers, legacy_headers): """Gather values from old headers.""" for legacy_header in legacy_headers: try: - value = headers[legacy_header.lower()] + value = _extract_header_value(headers, legacy_header.lower()) return value.split(',')[-1].strip() except KeyError: pass @@ -73,7 +74,7 @@ def check_legacy_headers(headers, legacy_headers): def check_standard_header(headers, service_type): """Parse the standard header to get value for service.""" try: - header = headers[STANDARD_HEADER] + header = _extract_header_value(headers, STANDARD_HEADER) for header_value in reversed(header.split(',')): try: service, version = header_value.strip().split(None, 1) @@ -100,3 +101,31 @@ def fold_headers(headers): folded_headers[header] = ','.join(value) return folded_headers + + +def headers_from_wsgi_environ(environ): + """Extract all the HTTP_ keys and values from environ to a new dict. + + Note that this does not change the keys in any way in the returned + dict. Nor is the incoming environ modified. + + :param environ: A PEP 3333 compliant WSGI environ dict. + """ + return {key: environ[key] for key in environ if key.startswith('HTTP_')} + + +def _extract_header_value(headers, header_name): + """Get the value of a header. + + The provided headers is a dict. If a key doesn't exist for + header_name, try using the WSGI environ form of the name. + + Raises KeyError if neither key is found. + """ + try: + value = headers[header_name] + except KeyError: + wsgi_header_name = ENVIRON_HTTP_HEADER_FMT.format( + header_name.replace('-', '_')) + value = headers[wsgi_header_name] + return value diff --git a/microversion_parse/tests/test_headers_from_wsgi_environ.py b/microversion_parse/tests/test_headers_from_wsgi_environ.py new file mode 100644 index 0000000..525ac6e --- /dev/null +++ b/microversion_parse/tests/test_headers_from_wsgi_environ.py @@ -0,0 +1,61 @@ +# 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 testtools + +import microversion_parse + + +class TestHeadersFromWSGIEnviron(testtools.TestCase): + + def test_empty_environ(self): + environ = {} + expected = {} + self.assertEqual( + expected, + microversion_parse.headers_from_wsgi_environ(environ)) + + def test_non_empty_no_headers(self): + environ = {'PATH_INFO': '/foo/bar'} + expected = {} + found_headers = microversion_parse.headers_from_wsgi_environ(environ) + self.assertEqual(expected, found_headers) + + def test_headers(self): + environ = {'PATH_INFO': '/foo/bar', + 'HTTP_OPENSTACK_API_VERSION': 'placement 2.1', + 'HTTP_CONTENT_TYPE': 'application/json'} + expected = {'HTTP_OPENSTACK_API_VERSION': 'placement 2.1', + 'HTTP_CONTENT_TYPE': 'application/json'} + found_headers = microversion_parse.headers_from_wsgi_environ(environ) + self.assertEqual(expected, found_headers) + + def test_get_version_from_environ(self): + environ = {'PATH_INFO': '/foo/bar', + 'HTTP_OPENSTACK_API_VERSION': 'placement 2.1', + 'HTTP_CONTENT_TYPE': 'application/json'} + expected_version = '2.1' + headers = microversion_parse.headers_from_wsgi_environ(environ) + version = microversion_parse.get_version(headers, 'placement') + self.assertEqual(expected_version, version) + + def test_get_version_from_environ_legacy(self): + environ = {'PATH_INFO': '/foo/bar', + 'HTTP_X_OPENSTACK_PLACEMENT_API_VERSION': '2.1', + 'HTTP_CONTENT_TYPE': 'application/json'} + expected_version = '2.1' + headers = microversion_parse.headers_from_wsgi_environ(environ) + version = microversion_parse.get_version( + headers, 'placement', + legacy_headers=['x-openstack-placement-api-version']) + self.assertEqual(expected_version, version)