diff options
author | Flaper Fesp <flaper87@gmail.com> | 2013-02-27 14:47:44 +0100 |
---|---|---|
committer | Flaper Fesp <flaper87@gmail.com> | 2013-03-29 13:50:48 +0100 |
commit | 03a4806d972ac150e717451566e9c4c35a141d8f (patch) | |
tree | b5079ee0b75eb9d184449913ac71691ba7a4f6c0 | |
parent | 7369310622224073ecbef4ab84a48c2a873a56b9 (diff) | |
download | python-cinderclient-03a4806d972ac150e717451566e9c4c35a141d8f.tar.gz |
Decodes input and encodes output1.0.3
Currently cinderclient doesn't handle properly incoming and outgoing
encode / decode process. As a solution for this, this patch implements a
decoding process for all data incoming from the user side and encodes
everything going out of the client, i.e: http requests, prints, etc.
This patch introduces a new module (strutils.py) taken from
oslo-incubator in order to use 2 of the functions present in it:
About safe_(decode|encode):
Both functions try to encode / decode the incoming text using the
stdin encoding, fallback to python's default encoding if that
returns None or to UTF-8 as the last option.
In both functions only basestring objects are accepted and they both
raise TypeError if an object of another type is passed.
About the general cinderclient changes:
In order to better support non-ASCII characters, it is a good
practice to use unicode interanlly and encode everything that has to
go out. This patch aims to do that and introduces this behaviour in
the client.
Testing:
A good test (besides using tox) is to use cinder client with and
without setting any locale (export LANG=).
Fixes bug: 1130572
Change-Id: Idb7d06954c29e003f68a0c4aa0b80ecc7017cbc9
-rw-r--r-- | cinderclient/openstack/common/strutils.py | 133 | ||||
-rw-r--r-- | cinderclient/shell.py | 8 | ||||
-rw-r--r-- | cinderclient/utils.py | 14 | ||||
-rw-r--r-- | openstack-common.conf | 2 |
4 files changed, 145 insertions, 12 deletions
diff --git a/cinderclient/openstack/common/strutils.py b/cinderclient/openstack/common/strutils.py new file mode 100644 index 0000000..7813b64 --- /dev/null +++ b/cinderclient/openstack/common/strutils.py @@ -0,0 +1,133 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# 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 logging +import sys + +LOG = logging.getLogger(__name__) + + +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): + """ + Interpret a string as a boolean. + + Any string value in: + + ('True', 'true', 'On', 'on', 'Yes', 'yes', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + if isinstance(subject, bool): + return subject + if isinstance(subject, basestring): + if subject.strip().lower() in ('true', 'on', 'yes', '1'): + return True + 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/cinderclient/shell.py b/cinderclient/shell.py index b163d70..c080564 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -30,6 +30,7 @@ import logging from cinderclient import client from cinderclient import exceptions as exc import cinderclient.extension +from cinderclient.openstack.common import strutils from cinderclient import utils from cinderclient.v1 import shell as shell_v1 from cinderclient.v2 import shell as shell_v2 @@ -486,13 +487,16 @@ class OpenStackHelpFormatter(argparse.HelpFormatter): def main(): try: - OpenStackCinderShell().main(sys.argv[1:]) + OpenStackCinderShell().main(map(strutils.safe_decode, sys.argv[1:])) except KeyboardInterrupt: print >> sys.stderr, "... terminating cinder client" sys.exit(130) except Exception, e: logger.debug(e, exc_info=1) - print >> sys.stderr, "ERROR: %s" % e.message + message = e.message + if not isinstance(message, basestring): + message = str(message) + print >> sys.stderr, "ERROR: %s" % strutils.safe_encode(message) sys.exit(1) diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 0e67508..f9c6566 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -1,4 +1,3 @@ -import locale import os import re import sys @@ -7,6 +6,7 @@ import uuid import prettytable from cinderclient import exceptions +from cinderclient.openstack.common import strutils def arg(*args, **kwargs): @@ -143,14 +143,14 @@ def print_list(objs, fields, formatters={}): row.append(data) pt.add_row(row) - print pt.get_string(sortby=fields[0]) + print strutils.safe_encode(pt.get_string(sortby=fields[0])) def print_dict(d, property="Property"): pt = prettytable.PrettyTable([property, 'Value'], caching=False) pt.aligns = ['l', 'l'] [pt.add_row(list(r)) for r in d.iteritems()] - print pt.get_string(sortby=property) + print strutils.safe_encode(pt.get_string(sortby=property)) def find_resource(manager, name_or_id): @@ -164,7 +164,7 @@ def find_resource(manager, name_or_id): # now try to get entity as uuid try: - uuid.UUID(str(name_or_id)) + uuid.UUID(strutils.safe_decode(name_or_id)) return manager.get(name_or_id) except (ValueError, exceptions.NotFound): pass @@ -180,11 +180,7 @@ def find_resource(manager, name_or_id): return manager.find(name=name_or_id) except exceptions.NotFound: try: - # For command-line arguments that are in Unicode - encoding = (locale.getpreferredencoding() or - sys.stdin.encoding or - 'UTF-8') - return manager.find(display_name=(name_or_id.decode(encoding))) + return manager.find(display_name=name_or_id) except (UnicodeDecodeError, exceptions.NotFound): try: # Volumes does not have name, but display_name diff --git a/openstack-common.conf b/openstack-common.conf index b4c453a..39d114c 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=setup,version +modules=setup,version,strutils # The base module to hold the copy of openstack.common base=cinderclient |