Make APIVersion's null check more pythonic

Our current APIVersion object has an is_null method to check when the
version instance is null (major=0 and minor=0).

While this works it is not very pythonic, since you have to write
expressions such as:

 if not min_version and not max_version:
     return True
 elif ((min_version and max_version) and
       max_version.is_null() and min_version.is_null()):
     return True

This patch removes the is_null method and instead implements the truth
value testing to simplify expressions and make code more pythonic.

So previous code would just look like:

 if not min_version and not max_version:
     return True

Because this will work with min_version being None or being an
APIVersion instance with major=0 and minor=0.

Change-Id: I7497c5dc940c1e726507117cadbad232d8c1d80d
This commit is contained in:
Gorka Eguileor 2016-08-05 14:45:28 +02:00
parent 8db4ca1d90
commit 8b5a772e36
4 changed files with 44 additions and 38 deletions

@ -75,13 +75,14 @@ class APIVersion(object):
% (self.ver_major, self.ver_minor)) % (self.ver_major, self.ver_minor))
def __repr__(self): def __repr__(self):
if self.is_null(): if self:
return "<APIVersion: null>"
else:
return "<APIVersion: %s>" % self.get_string() return "<APIVersion: %s>" % self.get_string()
return "<APIVersion: null>"
def is_null(self): def __bool__(self):
return self.ver_major == 0 and self.ver_minor == 0 return self.ver_major != 0 or self.ver_minor != 0
__nonzero__ = __bool__
def is_latest(self): def is_latest(self):
return self.ver_minor == float("inf") return self.ver_minor == float("inf")
@ -133,7 +134,7 @@ class APIVersion(object):
If self is null then raise ValueError If self is null then raise ValueError
""" """
if self.is_null(): if not self:
raise ValueError("Null APIVersion doesn't support 'matches'.") raise ValueError("Null APIVersion doesn't support 'matches'.")
if isinstance(min_version, str): if isinstance(min_version, str):
@ -141,24 +142,21 @@ class APIVersion(object):
if isinstance(max_version, str): if isinstance(max_version, str):
max_version = APIVersion(version_str=max_version) max_version = APIVersion(version_str=max_version)
# This will work when they are None and when they are version 0.0
if not min_version and not max_version: if not min_version and not max_version:
return True return True
elif ((min_version and max_version) and
max_version.is_null() and min_version.is_null()):
return True
elif not max_version or max_version.is_null(): if not max_version:
return min_version <= self return min_version <= self
elif not min_version or min_version.is_null(): if not min_version:
return self <= max_version return self <= max_version
else: return min_version <= self <= max_version
return min_version <= self <= max_version
def get_string(self): def get_string(self):
"""Converts object to string representation which if used to create """Converts object to string representation which if used to create
an APIVersion object results in the same version. an APIVersion object results in the same version.
""" """
if self.is_null(): if not self:
raise ValueError("Null APIVersion cannot be converted to string.") raise ValueError("Null APIVersion cannot be converted to string.")
elif self.is_latest(): elif self.is_latest():
return "%s.%s" % (self.ver_major, "latest") return "%s.%s" % (self.ver_major, "latest")
@ -208,8 +206,7 @@ def check_major_version(api_version):
supported supported
""" """
available_versions = get_available_major_versions() available_versions = get_available_major_versions()
if (not api_version.is_null() and if (api_version and str(api_version.ver_major) not in available_versions):
str(api_version.ver_major) not in available_versions):
if len(available_versions) == 1: if len(available_versions) == 1:
msg = ("Invalid client version '%(version)s'. " msg = ("Invalid client version '%(version)s'. "
"Major part should be '%(major)s'") % { "Major part should be '%(major)s'") % {
@ -260,9 +257,10 @@ def discover_version(client, requested_version):
server_start_version, server_end_version = _get_server_version_range( server_start_version, server_end_version = _get_server_version_range(
client) client)
both_versions_null = not (server_start_version or server_end_version)
if (not requested_version.is_latest() and if (not requested_version.is_latest() and
requested_version != APIVersion('2.0')): requested_version != APIVersion('2.0')):
if server_start_version.is_null() and server_end_version.is_null(): if both_versions_null:
raise exceptions.UnsupportedVersion( raise exceptions.UnsupportedVersion(
_("Server doesn't support microversions")) _("Server doesn't support microversions"))
if not requested_version.matches(server_start_version, if not requested_version.matches(server_start_version,
@ -275,18 +273,15 @@ def discover_version(client, requested_version):
return requested_version return requested_version
if requested_version == APIVersion('2.0'): if requested_version == APIVersion('2.0'):
if (server_start_version == APIVersion('2.1') or if server_start_version == APIVersion('2.1') or both_versions_null:
(server_start_version.is_null() and
server_end_version.is_null())):
return APIVersion('2.0') return APIVersion('2.0')
else: raise exceptions.UnsupportedVersion(
raise exceptions.UnsupportedVersion( _("The server isn't backward compatible with Cinder V2 REST "
_("The server isn't backward compatible with Cinder V2 REST " "API"))
"API"))
if server_start_version.is_null() and server_end_version.is_null(): if both_versions_null:
return APIVersion('2.0') return APIVersion('2.0')
elif cinderclient.API_MIN_VERSION > server_end_version: if cinderclient.API_MIN_VERSION > server_end_version:
raise exceptions.UnsupportedVersion( raise exceptions.UnsupportedVersion(
_("Server version is too old. The client valid version range is " _("Server version is too old. The client valid version range is "
"'%(client_min)s' to '%(client_max)s'. The server valid version " "'%(client_min)s' to '%(client_max)s'. The server valid version "
@ -315,7 +310,7 @@ def update_headers(headers, api_version):
null null
""" """
if not api_version.is_null() and api_version.ver_minor != 0: if api_version and api_version.ver_minor != 0:
headers["OpenStack-API-Version"] = "volume " + api_version.get_string() headers["OpenStack-API-Version"] = "volume " + api_version.get_string()
@ -326,7 +321,7 @@ def add_substitution(versioned_method):
def get_substitutions(func_name, api_version=None): def get_substitutions(func_name, api_version=None):
substitutions = _SUBSTITUTIONS.get(func_name, []) substitutions = _SUBSTITUTIONS.get(func_name, [])
if api_version and not api_version.is_null(): if api_version:
return [m for m in substitutions return [m for m in substitutions
if api_version.matches(m.start_version, m.end_version)] if api_version.matches(m.start_version, m.end_version)]
return substitutions return substitutions

@ -34,19 +34,19 @@ class UnsupportedAttribute(AttributeError):
""" """
def __init__(self, argument_name, start_version, end_version): def __init__(self, argument_name, start_version, end_version):
if not start_version.is_null() and not end_version.is_null(): if start_version and end_version:
self.message = ( self.message = (
"'%(name)s' argument is only allowed for microversions " "'%(name)s' argument is only allowed for microversions "
"%(start)s - %(end)s." % {"name": argument_name, "%(start)s - %(end)s." % {"name": argument_name,
"start": start_version.get_string(), "start": start_version.get_string(),
"end": end_version.get_string()}) "end": end_version.get_string()})
elif not start_version.is_null(): elif start_version:
self.message = ( self.message = (
"'%(name)s' argument is only allowed since microversion " "'%(name)s' argument is only allowed since microversion "
"%(start)s." % {"name": argument_name, "%(start)s." % {"name": argument_name,
"start": start_version.get_string()}) "start": start_version.get_string()})
elif not end_version.is_null(): elif end_version:
self.message = ( self.message = (
"'%(name)s' argument is not allowed after microversion " "'%(name)s' argument is not allowed after microversion "
"%(end)s." % {"name": argument_name, "%(end)s." % {"name": argument_name,

@ -433,11 +433,11 @@ class OpenStackCinderShell(object):
subparser.set_defaults(func=self.do_bash_completion) subparser.set_defaults(func=self.do_bash_completion)
def _build_versioned_help_message(self, start_version, end_version): def _build_versioned_help_message(self, start_version, end_version):
if not start_version.is_null() and not end_version.is_null(): if start_version and end_version:
msg = (_(" (Supported by API versions %(start)s - %(end)s)") msg = (_(" (Supported by API versions %(start)s - %(end)s)")
% {"start": start_version.get_string(), % {"start": start_version.get_string(),
"end": end_version.get_string()}) "end": end_version.get_string()})
elif not start_version.is_null(): elif start_version:
msg = (_(" (Supported by API version %(start)s and later)") msg = (_(" (Supported by API version %(start)s and later)")
% {"start": start_version.get_string()}) % {"start": start_version.get_string()})
else: else:
@ -504,8 +504,7 @@ class OpenStackCinderShell(object):
start_version = api_versions.APIVersion(start_version) start_version = api_versions.APIVersion(start_version)
end_version = kwargs.get('end_version', None) end_version = kwargs.get('end_version', None)
end_version = api_versions.APIVersion(end_version) end_version = api_versions.APIVersion(end_version)
if do_help and not (start_version.is_null() if do_help and (start_version or end_version):
and end_version.is_null()):
kwargs["help"] = kwargs.get("help", "") + ( kwargs["help"] = kwargs.get("help", "") + (
self._build_versioned_help_message(start_version, self._build_versioned_help_message(start_version,
end_version)) end_version))

@ -40,7 +40,11 @@ class APIVersionTestCase(utils.TestCase):
def test_null_version(self): def test_null_version(self):
v = api_versions.APIVersion() v = api_versions.APIVersion()
self.assertTrue(v.is_null()) self.assertFalse(v)
def test_not_null_version(self):
v = api_versions.APIVersion('1.1')
self.assertTrue(v)
@ddt.data("2", "200", "2.1.4", "200.23.66.3", "5 .3", "5. 3", "5.03", @ddt.data("2", "200", "2.1.4", "200.23.66.3", "5 .3", "5. 3", "5.03",
"02.1", "2.001", "", " 2.1", "2.1 ") "02.1", "2.001", "", " 2.1", "2.1 ")
@ -159,16 +163,24 @@ class GetAPIVersionTestCase(utils.TestCase):
self.assertRaises(exceptions.UnsupportedVersion, self.assertRaises(exceptions.UnsupportedVersion,
api_versions.get_api_version, "4") api_versions.get_api_version, "4")
@mock.patch("cinderclient.api_versions.get_available_major_versions")
@mock.patch("cinderclient.api_versions.APIVersion") @mock.patch("cinderclient.api_versions.APIVersion")
def test_only_major_part_is_presented(self, mock_apiversion): def test_only_major_part_is_presented(self, mock_apiversion,
mock_get_majors):
mock_get_majors.return_value = [
str(mock_apiversion.return_value.ver_major)]
version = 7 version = 7
self.assertEqual(mock_apiversion.return_value, self.assertEqual(mock_apiversion.return_value,
api_versions.get_api_version(version)) api_versions.get_api_version(version))
mock_apiversion.assert_called_once_with("%s.0" % str(version)) mock_apiversion.assert_called_once_with("%s.0" % str(version))
@mock.patch("cinderclient.api_versions.get_available_major_versions")
@mock.patch("cinderclient.api_versions.APIVersion") @mock.patch("cinderclient.api_versions.APIVersion")
def test_major_and_minor_parts_is_presented(self, mock_apiversion): def test_major_and_minor_parts_is_presented(self, mock_apiversion,
mock_get_majors):
version = "2.7" version = "2.7"
mock_get_majors.return_value = [
str(mock_apiversion.return_value.ver_major)]
self.assertEqual(mock_apiversion.return_value, self.assertEqual(mock_apiversion.return_value,
api_versions.get_api_version(version)) api_versions.get_api_version(version))
mock_apiversion.assert_called_once_with(version) mock_apiversion.assert_called_once_with(version)