Add ruff
Change-Id: I0a1bfae9996c1d866a200507b59f5c76753a1aca Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
@@ -12,6 +12,12 @@ repos:
|
|||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
files: .*\.(yaml|yml)$
|
files: .*\.(yaml|yml)$
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.13.0
|
||||||
|
hooks:
|
||||||
|
- id: ruff-check
|
||||||
|
args: ['--fix', '--unsafe-fixes']
|
||||||
|
- id: ruff-format
|
||||||
- repo: https://opendev.org/openstack/hacking
|
- repo: https://opendev.org/openstack/hacking
|
||||||
rev: 7.0.0
|
rev: 7.0.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (C) 2020 Red Hat, Inc.
|
# Copyright (C) 2020 Red Hat, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
'openstackdocstheme'
|
'openstackdocstheme',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
@@ -50,7 +49,7 @@ master_doc = 'index'
|
|||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = 'microversion-parse'
|
project = 'microversion-parse'
|
||||||
copyright = '2016, OpenStack'
|
copyright = '2016, OpenStack'
|
||||||
author = u'OpenStack'
|
author = 'OpenStack'
|
||||||
|
|
||||||
# openstackdocstheme options
|
# openstackdocstheme options
|
||||||
repository_name = 'openstack/microversion-parse'
|
repository_name = 'openstack/microversion-parse'
|
||||||
@@ -92,7 +91,7 @@ latex_documents = [
|
|||||||
'microversion-parse.tex',
|
'microversion-parse.tex',
|
||||||
'microversion-parse Documentation',
|
'microversion-parse Documentation',
|
||||||
'OpenStack',
|
'OpenStack',
|
||||||
'manual'
|
'manual',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -104,9 +103,9 @@ man_pages = [
|
|||||||
(
|
(
|
||||||
master_doc,
|
master_doc,
|
||||||
'microversion-parse',
|
'microversion-parse',
|
||||||
u'microversion-parse Documentation',
|
'microversion-parse Documentation',
|
||||||
[author],
|
[author],
|
||||||
1
|
1,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -120,11 +119,11 @@ texinfo_documents = [
|
|||||||
(
|
(
|
||||||
master_doc,
|
master_doc,
|
||||||
'microversion-parse',
|
'microversion-parse',
|
||||||
u'microversion-parse Documentation',
|
'microversion-parse Documentation',
|
||||||
author,
|
author,
|
||||||
'microversion-parse',
|
'microversion-parse',
|
||||||
'One line description of project.',
|
'One line description of project.',
|
||||||
'Miscellaneous'
|
'Miscellaneous',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -26,17 +26,16 @@ class Version(collections.namedtuple('Version', 'major minor')):
|
|||||||
|
|
||||||
def __new__(cls, major, minor):
|
def __new__(cls, major, minor):
|
||||||
"""Add min and max version attributes to the tuple."""
|
"""Add min and max version attributes to the tuple."""
|
||||||
self = super(Version, cls).__new__(cls, major, minor)
|
self = super().__new__(cls, major, minor)
|
||||||
self.max_version = (-1, 0)
|
self.max_version = (-1, 0)
|
||||||
self.min_version = (-1, 0)
|
self.min_version = (-1, 0)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s.%s' % (self.major, self.minor)
|
return f'{self.major}.{self.minor}'
|
||||||
|
|
||||||
def matches(self, min_version=None, max_version=None):
|
def matches(self, min_version=None, max_version=None):
|
||||||
"""Is this version within min_version and max_version.
|
"""Is this version within min_version and max_version."""
|
||||||
"""
|
|
||||||
# NOTE(cdent): min_version and max_version are expected
|
# NOTE(cdent): min_version and max_version are expected
|
||||||
# to be set by the code that is creating the Version, if
|
# to be set by the code that is creating the Version, if
|
||||||
# they are known.
|
# they are known.
|
||||||
@@ -155,7 +154,8 @@ def _extract_header_value(headers, header_name):
|
|||||||
value = headers[header_name]
|
value = headers[header_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
wsgi_header_name = ENVIRON_HTTP_HEADER_FMT.format(
|
wsgi_header_name = ENVIRON_HTTP_HEADER_FMT.format(
|
||||||
header_name.replace('-', '_'))
|
header_name.replace('-', '_')
|
||||||
|
)
|
||||||
value = headers[wsgi_header_name]
|
value = headers[wsgi_header_name]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -173,11 +173,9 @@ def parse_version_string(version_string):
|
|||||||
# ValueError, TypeError or AttributeError when the incoming
|
# ValueError, TypeError or AttributeError when the incoming
|
||||||
# data is poorly formed but will, however, naturally adapt to
|
# data is poorly formed but will, however, naturally adapt to
|
||||||
# extraneous whitespace.
|
# extraneous whitespace.
|
||||||
return Version(*(int(value) for value
|
return Version(*(int(value) for value in version_string.split('.', 1)))
|
||||||
in version_string.split('.', 1)))
|
|
||||||
except (ValueError, TypeError, AttributeError) as exc:
|
except (ValueError, TypeError, AttributeError) as exc:
|
||||||
raise TypeError('invalid version string: %s; %s' % (
|
raise TypeError(f'invalid version string: {version_string}; {exc}')
|
||||||
version_string, exc))
|
|
||||||
|
|
||||||
|
|
||||||
def extract_version(headers, service_type, versions_list):
|
def extract_version(headers, service_type, versions_list):
|
||||||
@@ -213,4 +211,4 @@ def extract_version(headers, service_type, versions_list):
|
|||||||
# to administratively disable a version if we really need to.
|
# to administratively disable a version if we really need to.
|
||||||
if str(request_version) in versions_list:
|
if str(request_version) in versions_list:
|
||||||
return request_version
|
return request_version
|
||||||
raise ValueError('Unacceptable version header: %s' % version_string)
|
raise ValueError(f'Unacceptable version header: {version_string}')
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import webob.dec
|
|||||||
import microversion_parse
|
import microversion_parse
|
||||||
|
|
||||||
|
|
||||||
class MicroversionMiddleware(object):
|
class MicroversionMiddleware:
|
||||||
"""WSGI middleware for getting microversion info.
|
"""WSGI middleware for getting microversion info.
|
||||||
|
|
||||||
The application will get a WSGI environ with a
|
The application will get a WSGI environ with a
|
||||||
@@ -37,8 +37,9 @@ class MicroversionMiddleware(object):
|
|||||||
Otherwise the application is called.
|
Otherwise the application is called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, application, service_type, versions,
|
def __init__(
|
||||||
json_error_formatter=None):
|
self, application, service_type, versions, json_error_formatter=None
|
||||||
|
):
|
||||||
"""Create the WSGI middleware.
|
"""Create the WSGI middleware.
|
||||||
|
|
||||||
:param application: The application hosting the service.
|
:param application: The application hosting the service.
|
||||||
@@ -51,7 +52,7 @@ class MicroversionMiddleware(object):
|
|||||||
"""
|
"""
|
||||||
self.application = application
|
self.application = application
|
||||||
self.service_type = service_type
|
self.service_type = service_type
|
||||||
self.microversion_environ = '%s.microversion' % service_type
|
self.microversion_environ = f'{service_type}.microversion'
|
||||||
self.versions = versions
|
self.versions = versions
|
||||||
self.json_error_formatter = json_error_formatter
|
self.json_error_formatter = json_error_formatter
|
||||||
|
|
||||||
@@ -59,21 +60,24 @@ class MicroversionMiddleware(object):
|
|||||||
def __call__(self, req):
|
def __call__(self, req):
|
||||||
try:
|
try:
|
||||||
microversion = microversion_parse.extract_version(
|
microversion = microversion_parse.extract_version(
|
||||||
req.headers, self.service_type, self.versions)
|
req.headers, self.service_type, self.versions
|
||||||
|
)
|
||||||
# TODO(cdent): These error response are not formatted according to
|
# TODO(cdent): These error response are not formatted according to
|
||||||
# api-sig guidelines, unless a json_error_formatter is provided
|
# api-sig guidelines, unless a json_error_formatter is provided
|
||||||
# that can do it. For an example, see the placement service.
|
# that can do it. For an example, see the placement service.
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise webob.exc.HTTPNotAcceptable(
|
raise webob.exc.HTTPNotAcceptable(
|
||||||
('Invalid microversion: %(error)s') % {'error': exc},
|
(f'Invalid microversion: {exc}'),
|
||||||
json_formatter=self.json_error_formatter)
|
json_formatter=self.json_error_formatter,
|
||||||
|
)
|
||||||
except TypeError as exc:
|
except TypeError as exc:
|
||||||
raise webob.exc.HTTPBadRequest(
|
raise webob.exc.HTTPBadRequest(
|
||||||
('Invalid microversion: %(error)s') % {'error': exc},
|
(f'Invalid microversion: {exc}'),
|
||||||
json_formatter=self.json_error_formatter)
|
json_formatter=self.json_error_formatter,
|
||||||
|
)
|
||||||
|
|
||||||
req.environ[self.microversion_environ] = microversion
|
req.environ[self.microversion_environ] = microversion
|
||||||
microversion_header = '%s %s' % (self.service_type, microversion)
|
microversion_header = f'{self.service_type} {microversion}'
|
||||||
standard_header = microversion_parse.STANDARD_HEADER
|
standard_header = microversion_parse.STANDARD_HEADER
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ import microversion_parse
|
|||||||
|
|
||||||
|
|
||||||
class TestVersion(testtools.TestCase):
|
class TestVersion(testtools.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestVersion, self).setUp()
|
super().setUp()
|
||||||
self.version = microversion_parse.Version(1, 5)
|
self.version = microversion_parse.Version(1, 5)
|
||||||
|
|
||||||
def test_version_is_tuple(self):
|
def test_version_is_tuple(self):
|
||||||
@@ -69,7 +68,6 @@ class TestVersion(testtools.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestParseVersionString(testtools.TestCase):
|
class TestParseVersionString(testtools.TestCase):
|
||||||
|
|
||||||
def test_good_version(self):
|
def test_good_version(self):
|
||||||
version = microversion_parse.parse_version_string('1.1')
|
version = microversion_parse.parse_version_string('1.1')
|
||||||
self.assertEqual((1, 1), version)
|
self.assertEqual((1, 1), version)
|
||||||
@@ -81,57 +79,68 @@ class TestParseVersionString(testtools.TestCase):
|
|||||||
self.assertEqual(microversion_parse.Version(1, 1), version)
|
self.assertEqual(microversion_parse.Version(1, 1), version)
|
||||||
|
|
||||||
def test_non_numeric(self):
|
def test_non_numeric(self):
|
||||||
self.assertRaises(TypeError,
|
self.assertRaises(
|
||||||
microversion_parse.parse_version_string,
|
TypeError, microversion_parse.parse_version_string, 'hello'
|
||||||
'hello')
|
)
|
||||||
|
|
||||||
def test_mixed_alphanumeric(self):
|
def test_mixed_alphanumeric(self):
|
||||||
self.assertRaises(TypeError,
|
self.assertRaises(
|
||||||
microversion_parse.parse_version_string,
|
TypeError, microversion_parse.parse_version_string, '1.a'
|
||||||
'1.a')
|
)
|
||||||
|
|
||||||
def test_too_many_numeric(self):
|
def test_too_many_numeric(self):
|
||||||
self.assertRaises(TypeError,
|
self.assertRaises(
|
||||||
microversion_parse.parse_version_string,
|
TypeError, microversion_parse.parse_version_string, '1.1.1'
|
||||||
'1.1.1')
|
)
|
||||||
|
|
||||||
def test_not_string(self):
|
def test_not_string(self):
|
||||||
self.assertRaises(TypeError,
|
self.assertRaises(
|
||||||
microversion_parse.parse_version_string,
|
TypeError, microversion_parse.parse_version_string, 1.1
|
||||||
1.1)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestExtractVersion(testtools.TestCase):
|
class TestExtractVersion(testtools.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestExtractVersion, self).setUp()
|
super().setUp()
|
||||||
self.headers = [
|
self.headers = [
|
||||||
('OpenStack-API-Version', 'service1 1.2'),
|
('OpenStack-API-Version', 'service1 1.2'),
|
||||||
('OpenStack-API-Version', 'service2 1.5'),
|
('OpenStack-API-Version', 'service2 1.5'),
|
||||||
('OpenStack-API-Version', 'service3 latest'),
|
('OpenStack-API-Version', 'service3 latest'),
|
||||||
('OpenStack-API-Version', 'service4 2.5'),
|
('OpenStack-API-Version', 'service4 2.5'),
|
||||||
]
|
]
|
||||||
self.version_list = ['1.1', '1.2', '1.3', '1.4',
|
self.version_list = [
|
||||||
'2.1', '2.2', '2.3', '2.4']
|
'1.1',
|
||||||
|
'1.2',
|
||||||
|
'1.3',
|
||||||
|
'1.4',
|
||||||
|
'2.1',
|
||||||
|
'2.2',
|
||||||
|
'2.3',
|
||||||
|
'2.4',
|
||||||
|
]
|
||||||
|
|
||||||
def test_simple_extract(self):
|
def test_simple_extract(self):
|
||||||
version = microversion_parse.extract_version(
|
version = microversion_parse.extract_version(
|
||||||
self.headers, 'service1', self.version_list)
|
self.headers, 'service1', self.version_list
|
||||||
|
)
|
||||||
self.assertEqual((1, 2), version)
|
self.assertEqual((1, 2), version)
|
||||||
|
|
||||||
def test_default_min(self):
|
def test_default_min(self):
|
||||||
version = microversion_parse.extract_version(
|
version = microversion_parse.extract_version(
|
||||||
self.headers, 'notlisted', self.version_list)
|
self.headers, 'notlisted', self.version_list
|
||||||
|
)
|
||||||
self.assertEqual((1, 1), version)
|
self.assertEqual((1, 1), version)
|
||||||
|
|
||||||
def test_latest(self):
|
def test_latest(self):
|
||||||
version = microversion_parse.extract_version(
|
version = microversion_parse.extract_version(
|
||||||
self.headers, 'service3', self.version_list)
|
self.headers, 'service3', self.version_list
|
||||||
|
)
|
||||||
self.assertEqual((2, 4), version)
|
self.assertEqual((2, 4), version)
|
||||||
|
|
||||||
def test_min_max_extract(self):
|
def test_min_max_extract(self):
|
||||||
version = microversion_parse.extract_version(
|
version = microversion_parse.extract_version(
|
||||||
self.headers, 'service1', self.version_list)
|
self.headers, 'service1', self.version_list
|
||||||
|
)
|
||||||
|
|
||||||
# below min
|
# below min
|
||||||
self.assertFalse(version.matches((1, 3)))
|
self.assertFalse(version.matches((1, 3)))
|
||||||
@@ -144,13 +153,24 @@ class TestExtractVersion(testtools.TestCase):
|
|||||||
# explicit min
|
# explicit min
|
||||||
self.assertFalse(version.matches(min_version=(2, 3)))
|
self.assertFalse(version.matches(min_version=(2, 3)))
|
||||||
# explicit both
|
# explicit both
|
||||||
self.assertTrue(version.matches(min_version=(0, 3),
|
self.assertTrue(
|
||||||
max_version=(1, 5)))
|
version.matches(min_version=(0, 3), max_version=(1, 5))
|
||||||
|
)
|
||||||
|
|
||||||
def test_version_disabled(self):
|
def test_version_disabled(self):
|
||||||
self.assertRaises(ValueError, microversion_parse.extract_version,
|
self.assertRaises(
|
||||||
self.headers, 'service2', self.version_list)
|
ValueError,
|
||||||
|
microversion_parse.extract_version,
|
||||||
|
self.headers,
|
||||||
|
'service2',
|
||||||
|
self.version_list,
|
||||||
|
)
|
||||||
|
|
||||||
def test_version_out_of_range(self):
|
def test_version_out_of_range(self):
|
||||||
self.assertRaises(ValueError, microversion_parse.extract_version,
|
self.assertRaises(
|
||||||
self.headers, 'service4', self.version_list)
|
ValueError,
|
||||||
|
microversion_parse.extract_version,
|
||||||
|
self.headers,
|
||||||
|
'service4',
|
||||||
|
self.version_list,
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import microversion_parse
|
|||||||
|
|
||||||
|
|
||||||
class TestFoldHeaders(testtools.TestCase):
|
class TestFoldHeaders(testtools.TestCase):
|
||||||
|
|
||||||
def test_dict_headers(self):
|
def test_dict_headers(self):
|
||||||
headers = {
|
headers = {
|
||||||
'header-one': 'alpha',
|
'header-one': 'alpha',
|
||||||
@@ -39,20 +38,19 @@ class TestFoldHeaders(testtools.TestCase):
|
|||||||
|
|
||||||
folded_headers = microversion_parse.fold_headers(headers)
|
folded_headers = microversion_parse.fold_headers(headers)
|
||||||
self.assertEqual(2, len(folded_headers))
|
self.assertEqual(2, len(folded_headers))
|
||||||
self.assertEqual(set(['header-one', 'header-two']),
|
self.assertEqual(
|
||||||
set(folded_headers.keys()))
|
set(['header-one', 'header-two']), set(folded_headers.keys())
|
||||||
|
)
|
||||||
self.assertEqual('alpha,gamma', folded_headers['header-one'])
|
self.assertEqual('alpha,gamma', folded_headers['header-one'])
|
||||||
|
|
||||||
def test_bad_headers(self):
|
def test_bad_headers(self):
|
||||||
headers = 'wow this is not a headers'
|
headers = 'wow this is not a headers'
|
||||||
self.assertRaises(ValueError, microversion_parse.fold_headers,
|
self.assertRaises(ValueError, microversion_parse.fold_headers, headers)
|
||||||
headers)
|
|
||||||
|
|
||||||
# TODO(cdent): Test with request objects from frameworks.
|
# TODO(cdent): Test with request objects from frameworks.
|
||||||
|
|
||||||
|
|
||||||
class TestStandardHeader(testtools.TestCase):
|
class TestStandardHeader(testtools.TestCase):
|
||||||
|
|
||||||
def test_simple_match(self):
|
def test_simple_match(self):
|
||||||
headers = {
|
headers = {
|
||||||
'header-one': 'alpha',
|
'header-one': 'alpha',
|
||||||
@@ -88,8 +86,7 @@ class TestStandardHeader(testtools.TestCase):
|
|||||||
'openstack-api-version': 'network 5.9 ',
|
'openstack-api-version': 'network 5.9 ',
|
||||||
'header-two': 'beta',
|
'header-two': 'beta',
|
||||||
}
|
}
|
||||||
version = microversion_parse.check_standard_header(
|
version = microversion_parse.check_standard_header(headers, 'compute')
|
||||||
headers, 'compute')
|
|
||||||
self.assertEqual(None, version)
|
self.assertEqual(None, version)
|
||||||
|
|
||||||
def test_match_multiple_services(self):
|
def test_match_multiple_services(self):
|
||||||
@@ -98,11 +95,11 @@ class TestStandardHeader(testtools.TestCase):
|
|||||||
'openstack-api-version': 'network 5.9 ,compute 2.1,telemetry 7.8',
|
'openstack-api-version': 'network 5.9 ,compute 2.1,telemetry 7.8',
|
||||||
'header-two': 'beta',
|
'header-two': 'beta',
|
||||||
}
|
}
|
||||||
version = microversion_parse.check_standard_header(
|
version = microversion_parse.check_standard_header(headers, 'compute')
|
||||||
headers, 'compute')
|
|
||||||
self.assertEqual('2.1', version)
|
self.assertEqual('2.1', version)
|
||||||
version = microversion_parse.check_standard_header(
|
version = microversion_parse.check_standard_header(
|
||||||
headers, 'telemetry')
|
headers, 'telemetry'
|
||||||
|
)
|
||||||
self.assertEqual('7.8', version)
|
self.assertEqual('7.8', version)
|
||||||
|
|
||||||
def test_match_multiple_same_service(self):
|
def test_match_multiple_same_service(self):
|
||||||
@@ -111,13 +108,11 @@ class TestStandardHeader(testtools.TestCase):
|
|||||||
'openstack-api-version': 'compute 5.9 ,compute 2.1,compute 7.8',
|
'openstack-api-version': 'compute 5.9 ,compute 2.1,compute 7.8',
|
||||||
'header-two': 'beta',
|
'header-two': 'beta',
|
||||||
}
|
}
|
||||||
version = microversion_parse.check_standard_header(
|
version = microversion_parse.check_standard_header(headers, 'compute')
|
||||||
headers, 'compute')
|
|
||||||
self.assertEqual('7.8', version)
|
self.assertEqual('7.8', version)
|
||||||
|
|
||||||
|
|
||||||
class TestLegacyHeaders(testtools.TestCase):
|
class TestLegacyHeaders(testtools.TestCase):
|
||||||
|
|
||||||
def test_legacy_headers_straight(self):
|
def test_legacy_headers_straight(self):
|
||||||
headers = {
|
headers = {
|
||||||
'header-one': 'alpha',
|
'header-one': 'alpha',
|
||||||
@@ -125,8 +120,10 @@ class TestLegacyHeaders(testtools.TestCase):
|
|||||||
'header-two': 'beta',
|
'header-two': 'beta',
|
||||||
}
|
}
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['openstack-CoMpUte-api-version'])
|
service_type='compute',
|
||||||
|
legacy_headers=['openstack-CoMpUte-api-version'],
|
||||||
|
)
|
||||||
self.assertEqual('2.1', version)
|
self.assertEqual('2.1', version)
|
||||||
|
|
||||||
def test_legacy_headers_folded(self):
|
def test_legacy_headers_folded(self):
|
||||||
@@ -136,8 +133,10 @@ class TestLegacyHeaders(testtools.TestCase):
|
|||||||
'header-two': 'beta',
|
'header-two': 'beta',
|
||||||
}
|
}
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['openstack-compute-api-version'])
|
service_type='compute',
|
||||||
|
legacy_headers=['openstack-compute-api-version'],
|
||||||
|
)
|
||||||
self.assertEqual('9.2', version)
|
self.assertEqual('9.2', version)
|
||||||
|
|
||||||
def test_older_legacy_headers(self):
|
def test_older_legacy_headers(self):
|
||||||
@@ -147,9 +146,13 @@ class TestLegacyHeaders(testtools.TestCase):
|
|||||||
'header-two': 'beta',
|
'header-two': 'beta',
|
||||||
}
|
}
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['openstack-nova-api-version',
|
service_type='compute',
|
||||||
'x-openstack-nova-api-version'])
|
legacy_headers=[
|
||||||
|
'openstack-nova-api-version',
|
||||||
|
'x-openstack-nova-api-version',
|
||||||
|
],
|
||||||
|
)
|
||||||
# We don't do x- for service types.
|
# We don't do x- for service types.
|
||||||
self.assertEqual('9.2', version)
|
self.assertEqual('9.2', version)
|
||||||
|
|
||||||
@@ -161,19 +164,26 @@ class TestLegacyHeaders(testtools.TestCase):
|
|||||||
'header-two': 'beta',
|
'header-two': 'beta',
|
||||||
}
|
}
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['openstack-compute-api-version',
|
service_type='compute',
|
||||||
'x-openstack-nova-api-version'])
|
legacy_headers=[
|
||||||
|
'openstack-compute-api-version',
|
||||||
|
'x-openstack-nova-api-version',
|
||||||
|
],
|
||||||
|
)
|
||||||
self.assertEqual('3.7', version)
|
self.assertEqual('3.7', version)
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['x-openstack-nova-api-version',
|
service_type='compute',
|
||||||
'openstack-compute-api-version'])
|
legacy_headers=[
|
||||||
|
'x-openstack-nova-api-version',
|
||||||
|
'openstack-compute-api-version',
|
||||||
|
],
|
||||||
|
)
|
||||||
self.assertEqual('9.2', version)
|
self.assertEqual('9.2', version)
|
||||||
|
|
||||||
|
|
||||||
class TestGetHeaders(testtools.TestCase):
|
class TestGetHeaders(testtools.TestCase):
|
||||||
|
|
||||||
def test_preference(self):
|
def test_preference(self):
|
||||||
headers = {
|
headers = {
|
||||||
'header-one': 'alpha',
|
'header-one': 'alpha',
|
||||||
@@ -183,15 +193,20 @@ class TestGetHeaders(testtools.TestCase):
|
|||||||
'header-two': 'beta',
|
'header-two': 'beta',
|
||||||
}
|
}
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['openstack-compute-api-version',
|
service_type='compute',
|
||||||
'x-openstack-nova-api-version'])
|
legacy_headers=[
|
||||||
|
'openstack-compute-api-version',
|
||||||
|
'x-openstack-nova-api-version',
|
||||||
|
],
|
||||||
|
)
|
||||||
self.assertEqual('11.12', version)
|
self.assertEqual('11.12', version)
|
||||||
|
|
||||||
def test_no_headers(self):
|
def test_no_headers(self):
|
||||||
headers = {}
|
headers = {}
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute')
|
headers, service_type='compute'
|
||||||
|
)
|
||||||
self.assertEqual(None, version)
|
self.assertEqual(None, version)
|
||||||
|
|
||||||
def test_unfolded_service(self):
|
def test_unfolded_service(self):
|
||||||
@@ -202,7 +217,8 @@ class TestGetHeaders(testtools.TestCase):
|
|||||||
('openstack-api-version', '3.0'),
|
('openstack-api-version', '3.0'),
|
||||||
]
|
]
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute')
|
headers, service_type='compute'
|
||||||
|
)
|
||||||
self.assertEqual('2.0', version)
|
self.assertEqual('2.0', version)
|
||||||
|
|
||||||
def test_unfolded_in_name(self):
|
def test_unfolded_in_name(self):
|
||||||
@@ -213,15 +229,17 @@ class TestGetHeaders(testtools.TestCase):
|
|||||||
('openstack-telemetry-api-version', '3.0'),
|
('openstack-telemetry-api-version', '3.0'),
|
||||||
]
|
]
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['x-openstack-nova-api-version'])
|
service_type='compute',
|
||||||
|
legacy_headers=['x-openstack-nova-api-version'],
|
||||||
|
)
|
||||||
self.assertEqual('2.0', version)
|
self.assertEqual('2.0', version)
|
||||||
|
|
||||||
def test_capitalized_headers(self):
|
def test_capitalized_headers(self):
|
||||||
headers = {
|
headers = {'X-Openstack-Ironic-Api-Version': '123.456'}
|
||||||
'X-Openstack-Ironic-Api-Version': '123.456'
|
|
||||||
}
|
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='ironic',
|
headers,
|
||||||
legacy_headers=['X-Openstack-Ironic-Api-Version'])
|
service_type='ironic',
|
||||||
|
legacy_headers=['X-Openstack-Ironic-Api-Version'],
|
||||||
|
)
|
||||||
self.assertEqual('123.456', version)
|
self.assertEqual('123.456', version)
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ import microversion_parse
|
|||||||
|
|
||||||
|
|
||||||
class TestHeadersFromWSGIEnviron(testtools.TestCase):
|
class TestHeadersFromWSGIEnviron(testtools.TestCase):
|
||||||
|
|
||||||
def test_empty_environ(self):
|
def test_empty_environ(self):
|
||||||
environ = {}
|
environ = {}
|
||||||
expected = {}
|
expected = {}
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
expected,
|
expected, microversion_parse.headers_from_wsgi_environ(environ)
|
||||||
microversion_parse.headers_from_wsgi_environ(environ))
|
)
|
||||||
|
|
||||||
def test_non_empty_no_headers(self):
|
def test_non_empty_no_headers(self):
|
||||||
environ = {'PATH_INFO': '/foo/bar'}
|
environ = {'PATH_INFO': '/foo/bar'}
|
||||||
@@ -32,30 +31,40 @@ class TestHeadersFromWSGIEnviron(testtools.TestCase):
|
|||||||
self.assertEqual(expected, found_headers)
|
self.assertEqual(expected, found_headers)
|
||||||
|
|
||||||
def test_headers(self):
|
def test_headers(self):
|
||||||
environ = {'PATH_INFO': '/foo/bar',
|
environ = {
|
||||||
'HTTP_OPENSTACK_API_VERSION': 'placement 2.1',
|
'PATH_INFO': '/foo/bar',
|
||||||
'HTTP_CONTENT_TYPE': 'application/json'}
|
'HTTP_OPENSTACK_API_VERSION': 'placement 2.1',
|
||||||
expected = {'HTTP_OPENSTACK_API_VERSION': 'placement 2.1',
|
'HTTP_CONTENT_TYPE': 'application/json',
|
||||||
'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)
|
found_headers = microversion_parse.headers_from_wsgi_environ(environ)
|
||||||
self.assertEqual(expected, found_headers)
|
self.assertEqual(expected, found_headers)
|
||||||
|
|
||||||
def test_get_version_from_environ(self):
|
def test_get_version_from_environ(self):
|
||||||
environ = {'PATH_INFO': '/foo/bar',
|
environ = {
|
||||||
'HTTP_OPENSTACK_API_VERSION': 'placement 2.1',
|
'PATH_INFO': '/foo/bar',
|
||||||
'HTTP_CONTENT_TYPE': 'application/json'}
|
'HTTP_OPENSTACK_API_VERSION': 'placement 2.1',
|
||||||
|
'HTTP_CONTENT_TYPE': 'application/json',
|
||||||
|
}
|
||||||
expected_version = '2.1'
|
expected_version = '2.1'
|
||||||
headers = microversion_parse.headers_from_wsgi_environ(environ)
|
headers = microversion_parse.headers_from_wsgi_environ(environ)
|
||||||
version = microversion_parse.get_version(headers, 'placement')
|
version = microversion_parse.get_version(headers, 'placement')
|
||||||
self.assertEqual(expected_version, version)
|
self.assertEqual(expected_version, version)
|
||||||
|
|
||||||
def test_get_version_from_environ_legacy(self):
|
def test_get_version_from_environ_legacy(self):
|
||||||
environ = {'PATH_INFO': '/foo/bar',
|
environ = {
|
||||||
'HTTP_X_OPENSTACK_PLACEMENT_API_VERSION': '2.1',
|
'PATH_INFO': '/foo/bar',
|
||||||
'HTTP_CONTENT_TYPE': 'application/json'}
|
'HTTP_X_OPENSTACK_PLACEMENT_API_VERSION': '2.1',
|
||||||
|
'HTTP_CONTENT_TYPE': 'application/json',
|
||||||
|
}
|
||||||
expected_version = '2.1'
|
expected_version = '2.1'
|
||||||
headers = microversion_parse.headers_from_wsgi_environ(environ)
|
headers = microversion_parse.headers_from_wsgi_environ(environ)
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, 'placement',
|
headers,
|
||||||
legacy_headers=['x-openstack-placement-api-version'])
|
'placement',
|
||||||
|
legacy_headers=['x-openstack-placement-api-version'],
|
||||||
|
)
|
||||||
self.assertEqual(expected_version, version)
|
self.assertEqual(expected_version, version)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ VERSIONS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SimpleWSGI(object):
|
class SimpleWSGI:
|
||||||
"""A WSGI application that can be contiained within a middlware."""
|
"""A WSGI application that can be contiained within a middlware."""
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
@@ -41,12 +41,13 @@ class SimpleWSGI(object):
|
|||||||
start_response('200 OK', [('content-type', 'text/plain')])
|
start_response('200 OK', [('content-type', 'text/plain')])
|
||||||
return [b'good']
|
return [b'good']
|
||||||
|
|
||||||
raise webob.exc.HTTPNotFound('%s not found' % path_info)
|
raise webob.exc.HTTPNotFound(f'{path_info} not found')
|
||||||
|
|
||||||
|
|
||||||
def app():
|
def app():
|
||||||
app = middleware.MicroversionMiddleware(
|
app = middleware.MicroversionMiddleware(
|
||||||
SimpleWSGI(), SERVICE_TYPE, VERSIONS)
|
SimpleWSGI(), SERVICE_TYPE, VERSIONS
|
||||||
|
)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
@@ -54,4 +55,5 @@ def load_tests(loader, tests, pattern):
|
|||||||
"""Provide a TestSuite to the discovery process."""
|
"""Provide a TestSuite to the discovery process."""
|
||||||
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
|
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
|
||||||
return driver.build_tests(
|
return driver.build_tests(
|
||||||
test_dir, loader, test_loader_name=__name__, intercept=app)
|
test_dir, loader, test_loader_name=__name__, intercept=app
|
||||||
|
)
|
||||||
|
|||||||
@@ -22,59 +22,74 @@ class TestWebobHeaders(testtools.TestCase):
|
|||||||
"""Webob uses a dict-like header which is not actually a dict."""
|
"""Webob uses a dict-like header which is not actually a dict."""
|
||||||
|
|
||||||
def test_simple_headers(self):
|
def test_simple_headers(self):
|
||||||
headers = wb_headers.EnvironHeaders({
|
headers = wb_headers.EnvironHeaders(
|
||||||
'HTTP_HEADER_ONE': 'alpha',
|
{
|
||||||
'HTTP_HEADER_TWO': 'beta',
|
'HTTP_HEADER_ONE': 'alpha',
|
||||||
'HTTP_HEADER_THREE': 'gamma',
|
'HTTP_HEADER_TWO': 'beta',
|
||||||
})
|
'HTTP_HEADER_THREE': 'gamma',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
folded_headers = microversion_parse.fold_headers(headers)
|
folded_headers = microversion_parse.fold_headers(headers)
|
||||||
self.assertEqual(3, len(folded_headers))
|
self.assertEqual(3, len(folded_headers))
|
||||||
self.assertEqual(set(['header-one', 'header-three', 'header-two']),
|
self.assertEqual(
|
||||||
set(folded_headers.keys()))
|
set(['header-one', 'header-three', 'header-two']),
|
||||||
|
set(folded_headers.keys()),
|
||||||
|
)
|
||||||
self.assertEqual('gamma', folded_headers['header-three'])
|
self.assertEqual('gamma', folded_headers['header-three'])
|
||||||
|
|
||||||
def test_simple_match(self):
|
def test_simple_match(self):
|
||||||
headers = wb_headers.EnvironHeaders({
|
headers = wb_headers.EnvironHeaders(
|
||||||
'HTTP_HEADER_ONE': 'alpha',
|
{
|
||||||
'HTTP_OPENSTACK_API_VERSION': 'compute 2.1',
|
'HTTP_HEADER_ONE': 'alpha',
|
||||||
'HTTP_HEADER_TWO': 'beta',
|
'HTTP_OPENSTACK_API_VERSION': 'compute 2.1',
|
||||||
})
|
'HTTP_HEADER_TWO': 'beta',
|
||||||
|
}
|
||||||
|
)
|
||||||
version = microversion_parse.check_standard_header(headers, 'compute')
|
version = microversion_parse.check_standard_header(headers, 'compute')
|
||||||
self.assertEqual('2.1', version)
|
self.assertEqual('2.1', version)
|
||||||
|
|
||||||
def test_match_multiple_services(self):
|
def test_match_multiple_services(self):
|
||||||
headers = wb_headers.EnvironHeaders({
|
headers = wb_headers.EnvironHeaders(
|
||||||
'HTTP_HEADER_ONE': 'alpha',
|
{
|
||||||
'HTTP_OPENSTACK_API_VERSION':
|
'HTTP_HEADER_ONE': 'alpha',
|
||||||
'network 5.9 ,compute 2.1,telemetry 7.8',
|
'HTTP_OPENSTACK_API_VERSION': 'network 5.9 ,compute 2.1,telemetry 7.8',
|
||||||
'HTTP_HEADER_TWO': 'beta',
|
'HTTP_HEADER_TWO': 'beta',
|
||||||
})
|
}
|
||||||
version = microversion_parse.check_standard_header(
|
)
|
||||||
headers, 'compute')
|
version = microversion_parse.check_standard_header(headers, 'compute')
|
||||||
self.assertEqual('2.1', version)
|
self.assertEqual('2.1', version)
|
||||||
version = microversion_parse.check_standard_header(
|
version = microversion_parse.check_standard_header(
|
||||||
headers, 'telemetry')
|
headers, 'telemetry'
|
||||||
|
)
|
||||||
self.assertEqual('7.8', version)
|
self.assertEqual('7.8', version)
|
||||||
|
|
||||||
def test_legacy_headers_straight(self):
|
def test_legacy_headers_straight(self):
|
||||||
headers = wb_headers.EnvironHeaders({
|
headers = wb_headers.EnvironHeaders(
|
||||||
'HTTP_HEADER_ONE': 'alpha',
|
{
|
||||||
'HTTP_X_OPENSTACK_NOVA_API_VERSION': ' 2.1 ',
|
'HTTP_HEADER_ONE': 'alpha',
|
||||||
'HTTP_HEADER_TWO': 'beta',
|
'HTTP_X_OPENSTACK_NOVA_API_VERSION': ' 2.1 ',
|
||||||
})
|
'HTTP_HEADER_TWO': 'beta',
|
||||||
|
}
|
||||||
|
)
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['x-openstack-nova-api-version'])
|
service_type='compute',
|
||||||
|
legacy_headers=['x-openstack-nova-api-version'],
|
||||||
|
)
|
||||||
self.assertEqual('2.1', version)
|
self.assertEqual('2.1', version)
|
||||||
|
|
||||||
def test_legacy_headers_folded(self):
|
def test_legacy_headers_folded(self):
|
||||||
headers = wb_headers.EnvironHeaders({
|
headers = wb_headers.EnvironHeaders(
|
||||||
'HTTP_HEADER_ONE': 'alpha',
|
{
|
||||||
'HTTP_X_OPENSTACK_NOVA_API_VERSION': ' 2.1, 9.2 ',
|
'HTTP_HEADER_ONE': 'alpha',
|
||||||
'HTTP_HEADER_TWO': 'beta',
|
'HTTP_X_OPENSTACK_NOVA_API_VERSION': ' 2.1, 9.2 ',
|
||||||
})
|
'HTTP_HEADER_TWO': 'beta',
|
||||||
|
}
|
||||||
|
)
|
||||||
version = microversion_parse.get_version(
|
version = microversion_parse.get_version(
|
||||||
headers, service_type='compute',
|
headers,
|
||||||
legacy_headers=['x-openstack-nova-api-version'])
|
service_type='compute',
|
||||||
|
legacy_headers=['x-openstack-nova-api-version'],
|
||||||
|
)
|
||||||
self.assertEqual('9.2', version)
|
self.assertEqual('9.2', version)
|
||||||
|
|||||||
@@ -33,3 +33,20 @@ Repository = "https://opendev.org/openstack/microversion-parse"
|
|||||||
packages = [
|
packages = [
|
||||||
"microversion_parse"
|
"microversion_parse"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 79
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "single"
|
||||||
|
docstring-code-format = true
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E4", "E7", "E9", "F", "S", "U"]
|
||||||
|
ignore = [
|
||||||
|
# we only use asserts for type narrowing
|
||||||
|
"S101",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"microversion_parse/tests/*" = ["S"]
|
||||||
|
|||||||
3
setup.py
3
setup.py
@@ -15,4 +15,5 @@ import setuptools
|
|||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
setup_requires=['pbr'],
|
setup_requires=['pbr'],
|
||||||
pbr=True)
|
pbr=True,
|
||||||
|
)
|
||||||
|
|||||||
4
tox.ini
4
tox.ini
@@ -48,6 +48,8 @@ whitelist_externals =
|
|||||||
rm
|
rm
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore = H405,E126
|
# We only enable the hacking (H) checks
|
||||||
|
select = H
|
||||||
|
ignore = H405
|
||||||
exclude = .venv,.git,.tox,dist,*egg,*.egg-info,build,examples,doc
|
exclude = .venv,.git,.tox,dist,*egg,*.egg-info,build,examples,doc
|
||||||
show-source = true
|
show-source = true
|
||||||
|
|||||||
Reference in New Issue
Block a user