From 9356ea634d17120c31e4845d58d1043ec7ac6fe0 Mon Sep 17 00:00:00 2001 From: Michael Still Date: Fri, 17 May 2013 10:05:14 +1000 Subject: [PATCH] Import strutils from oslo, and convert to it. The only change of behaviour here (according to the former glance unit tests) is that 'T' now means True not False. I suspect that's ok. Change-Id: Icf98f913a3f78a1fe2f38a82a25a071c2da03739 --- glance/api/v1/images.py | 3 +- glance/common/utils.py | 15 +-- glance/openstack/common/strutils.py | 150 +++++++++++++++++++++++++ glance/registry/api/v1/images.py | 3 +- glance/tests/unit/common/test_utils.py | 7 -- glance/tests/unit/test_misc.py | 17 --- openstack-common.conf | 1 + 7 files changed, 157 insertions(+), 39 deletions(-) create mode 100644 glance/openstack/common/strutils.py diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py index 8b33018f33..cff2f7cdf4 100644 --- a/glance/api/v1/images.py +++ b/glance/api/v1/images.py @@ -44,6 +44,7 @@ from glance.common import utils from glance.common import wsgi from glance import notifier import glance.openstack.common.log as logging +from glance.openstack.common import strutils import glance.registry.client.v1.api as registry from glance.store import (get_from_backend, get_size_from_backend, @@ -634,7 +635,7 @@ class Controller(controller.BaseController): # properties NOT to be purged. However we also disable purging of # properties if an image file is being uploaded... purge_props = req.headers.get('x-glance-registry-purge-props', True) - purge_props = (utils.bool_from_string(purge_props) and + purge_props = (strutils.bool_from_string(purge_props) and image_data is None) if image_data is not None and orig_status != 'queued': diff --git a/glance/common/utils.py b/glance/common/utils.py index 96719cabf7..bd3b86eea2 100644 --- a/glance/common/utils.py +++ b/glance/common/utils.py @@ -41,6 +41,7 @@ from webob import exc from glance.common import exception import glance.openstack.common.log as logging +from glance.openstack.common import strutils CONF = cfg.CONF @@ -245,22 +246,10 @@ def get_image_meta_from_headers(response): raise exception.Invalid for key in ('is_public', 'deleted', 'protected'): if key in result: - result[key] = bool_from_string(result[key]) + result[key] = strutils.bool_from_string(result[key]) return result -def bool_from_string(subject): - """Interpret a string as a boolean-like value.""" - if isinstance(subject, bool): - return subject - elif isinstance(subject, int): - return subject == 1 - if hasattr(subject, 'startswith'): # str or unicode... - if subject.strip().lower() in ('true', 'on', '1', 'yes', 'y'): - return True - return False - - def safe_mkdirs(path): try: os.makedirs(path) diff --git a/glance/openstack/common/strutils.py b/glance/openstack/common/strutils.py new file mode 100644 index 0000000000..3dd0854ad4 --- /dev/null +++ b/glance/openstack/common/strutils.py @@ -0,0 +1,150 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +""" +System-level utilities and helper functions. +""" + +import sys + +from glance.openstack.common.gettextutils import _ + + +TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') +FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') + + +def int_from_bool_as_string(subject): + """ + Interpret a string as a boolean and return either 1 or 0. + + Any string value in: + + ('True', 'true', 'On', 'on', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + return bool_from_string(subject) and 1 or 0 + + +def bool_from_string(subject, strict=False): + """ + Interpret a string as a boolean. + + A case-insensitive match is performed such that strings matching 't', + 'true', 'on', 'y', 'yes', or '1' are considered True and, when + `strict=False`, anything else is considered False. + + Useful for JSON-decoded stuff and config file parsing. + + If `strict=True`, unrecognized values, including None, will raise a + ValueError which is useful when parsing values passed in from an API call. + Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. + """ + if not isinstance(subject, basestring): + subject = str(subject) + + lowered = subject.strip().lower() + + if lowered in TRUE_STRINGS: + return True + elif lowered in FALSE_STRINGS: + return False + elif strict: + acceptable = ', '.join( + "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) + msg = _("Unrecognized value '%(val)s', acceptable values are:" + " %(acceptable)s") % {'val': subject, + 'acceptable': acceptable} + raise ValueError(msg) + else: + return False + + +def safe_decode(text, incoming=None, errors='strict'): + """ + Decodes incoming str using `incoming` if they're + not already unicode. + + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a unicode `incoming` encoded + representation of it. + :raises TypeError: If text is not an isntance of basestring + """ + if not isinstance(text, basestring): + raise TypeError("%s can't be decoded" % type(text)) + + if isinstance(text, unicode): + return text + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + try: + return text.decode(incoming, errors) + except UnicodeDecodeError: + # Note(flaper87) If we get here, it means that + # sys.stdin.encoding / sys.getdefaultencoding + # didn't return a suitable encoding to decode + # text. This happens mostly when global LANG + # var is not set correctly and there's no + # default encoding. In this case, most likely + # python will use ASCII or ANSI encoders as + # default encodings but they won't be capable + # of decoding non-ASCII characters. + # + # Also, UTF-8 is being used since it's an ASCII + # extension. + return text.decode('utf-8', errors) + + +def safe_encode(text, incoming=None, + encoding='utf-8', errors='strict'): + """ + Encodes incoming str/unicode using `encoding`. If + incoming is not specified, text is expected to + be encoded with current python's default encoding. + (`sys.getdefaultencoding`) + + :param incoming: Text's current encoding + :param encoding: Expected encoding for text (Default UTF-8) + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a bytestring `encoding` encoded + representation of it. + :raises TypeError: If text is not an isntance of basestring + """ + if not isinstance(text, basestring): + raise TypeError("%s can't be encoded" % type(text)) + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + if isinstance(text, unicode): + return text.encode(encoding, errors) + elif text and encoding != incoming: + # Decode text before encoding it with `encoding` + text = safe_decode(text, incoming, errors) + return text.encode(encoding, errors) + + return text diff --git a/glance/registry/api/v1/images.py b/glance/registry/api/v1/images.py index 6ffe4723d1..f4d6178bec 100644 --- a/glance/registry/api/v1/images.py +++ b/glance/registry/api/v1/images.py @@ -27,6 +27,7 @@ from glance.common import utils from glance.common import wsgi import glance.db import glance.openstack.common.log as logging +from glance.openstack.common import strutils from glance.openstack.common import timeutils from glance.openstack.common import uuidutils @@ -273,7 +274,7 @@ class Controller(object): deleted = req.params.get('deleted') if deleted is None: return None - return utils.bool_from_string(deleted) + return strutils.bool_from_string(deleted) def show(self, req, id): """Return data about the given image id.""" diff --git a/glance/tests/unit/common/test_utils.py b/glance/tests/unit/common/test_utils.py index eb034b8d10..3db4dc272a 100644 --- a/glance/tests/unit/common/test_utils.py +++ b/glance/tests/unit/common/test_utils.py @@ -118,13 +118,6 @@ class TestUtils(test_utils.BaseTestCase): self.assertRaises(exception.ImageSizeLimitExceeded, _consume_all_read) - def test_bool_from_string(self): - actual = utils.bool_from_string('true') - self.assertEqual(True, actual) - - actual = utils.bool_from_string(1) - self.assertEqual(True, actual) - def test_get_meta_from_headers(self): resp = webob.Response() resp.headers = {"x-image-meta-*": 'test'} diff --git a/glance/tests/unit/test_misc.py b/glance/tests/unit/test_misc.py index b9ca5e4152..7bfd354151 100644 --- a/glance/tests/unit/test_misc.py +++ b/glance/tests/unit/test_misc.py @@ -24,23 +24,6 @@ from glance.tests import utils as test_utils class UtilsTestCase(test_utils.BaseTestCase): - def test_bool_from_string(self): - true_values = ['True', True, 'true', 'TRUE', '1', 1, 'on', - 'ON', 'y', 'yes', 'Y', 'YES'] - - i = 0 - for value in true_values: - self.assertTrue(utils.bool_from_string(value), - "Got False for value: %r (%d)" % (value, i)) - i = i + 1 - - false_values = ['False', False, 'false', 'T', 'F', 'FALSE', - '0', 0, 9, 'off', 'OFF', 'no', 'n', 'NO', 'N'] - - for value in false_values: - self.assertFalse(utils.bool_from_string(value), - "Got True for value: %r" % value) - def test_encryption(self): # Check that original plaintext and unencrypted ciphertext match # Check keys of the three allowed lengths diff --git a/openstack-common.conf b/openstack-common.conf index 746b58f384..6e3ff0847a 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -10,6 +10,7 @@ module=log module=notifier module=policy module=setup +module=strutils module=timeutils module=uuidutils module=version