From e49e812900bbad9ff37e60258cfe2854e67e325b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 1 Oct 2015 16:04:59 +0200 Subject: [PATCH] Add utilities for base64 Writing a code using the base64 module (of the Python standard library) working on Python 2.7 and 3.4 requires many checks on the input and/or output type: * base64.b64encode() only accepts byte string: text must be explicitly encoded to ASCII * base64.b64decode() returns bytes: output must be decoded from UTF-8 when text is expected This change adds two pairs of encode/decode functions: * encode_as_bytes(), decode_as_bytes(): always return the result as a byte string * encode_as_text(), decode_as_text(): always return the result as a text string Encode functions accept text: text is encoded to UTF-8 by default, but the encoding is configurable. Decode functions accept text: text is decoded from ASCII. decode_as_text() decodes the result from UTF-8 by default, but again the encoding is configurable. The new submodule is called "base64" to be able to replace: import base64 with: from oslo_serialization import base64 If the base64 module of the stdlib is needed, it can be imported as a different name. Example: import base64 as std_base64 The encoding example: if isinstance(text, six.text_type): text = text.encode('utf-8') text_b64 = base64.b64encode(text) text_b64 = text_b64.decode('ascii') can be replaced with: text_b64 = base64.encode_as_text(text) The decoding example: if isinstance(encoded, six.text_type): encoded = encoded.decode('ascii') text = base64.b64decode(encoded) text = text.decode('utf-8') can be replaced with: text = base64.decode_as_text(text) Change-Id: Icf8df9c947bc0c5f4838508b756ed8f53efd9fc4 --- README.rst | 2 +- doc/source/api.rst | 6 ++ oslo_serialization/base64.py | 79 +++++++++++++++++++++++++ oslo_serialization/tests/test_base64.py | 56 ++++++++++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 oslo_serialization/base64.py create mode 100644 oslo_serialization/tests/test_base64.py diff --git a/README.rst b/README.rst index e4d7fa8..5d72089 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ :alt: Downloads The oslo.serialization library provides support for representing objects -in transmittable and storable formats, such as JSON and MessagePack. +in transmittable and storable formats, such as Base64, JSON and MessagePack. * Free software: Apache license * Documentation: http://docs.openstack.org/developer/oslo.serialization diff --git a/doc/source/api.rst b/doc/source/api.rst index c722b3a..85589b1 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -1,3 +1,9 @@ +base64 +====== + +.. automodule:: oslo_serialization.base64 + :members: + jsonutils ========= diff --git a/oslo_serialization/base64.py b/oslo_serialization/base64.py new file mode 100644 index 0000000..0223f6a --- /dev/null +++ b/oslo_serialization/base64.py @@ -0,0 +1,79 @@ +# Copyright 2015 Red Hat +# 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. + +from __future__ import absolute_import + +import base64 + +import six + + +def encode_as_bytes(s, encoding='utf-8'): + """Encode a string using Base64. + + If *s* is a text string, first encode it to *encoding* (UTF-8 by default). + + :param s: bytes or text string to be encoded + :param encoding: encoding used to encode *s* if it's a text string + :returns: Base64 encoded byte string (bytes) + + Use encode_as_text() to get the Base64 encoded string as text. + """ + if isinstance(s, six.text_type): + s = s.encode(encoding) + return base64.b64encode(s) + + +def encode_as_text(s, encoding='utf-8'): + """Encode a string using Base64. + + If *s* is a text string, first encode it to *encoding* (UTF-8 by default). + + :param s: bytes or text string to be encoded + :param encoding: encoding used to encode *s* if it's a text string + :returns: Base64 encoded text string (Unicode) + + Use encode_as_bytes() to get the Base64 encoded string as bytes. + """ + encoded = encode_as_bytes(s, encoding=encoding) + return encoded.decode('ascii') + + +def decode_as_bytes(encoded): + """Decode a Base64 encoded string. + + :param encoded: bytes or text Base64 encoded string to be decoded + :returns: decoded bytes string (bytes) + + Use decode_as_text() to get the decoded string as text. + """ + if isinstance(encoded, bytes): + encoded = encoded.decode('ascii') + return base64.b64decode(encoded) + + +def decode_as_text(encoded, encoding='utf-8'): + """Decode a Base64 encoded string. + + Decode the Base64 string and then decode the result from *encoding* + (UTF-8 by default). + + :param encoded: bytes or text Base64 encoded string to be decoded + :returns: decoded text string (bytes) + + Use decode_as_bytes() to get the decoded string as bytes. + """ + decoded = decode_as_bytes(encoded) + return decoded.decode(encoding) diff --git a/oslo_serialization/tests/test_base64.py b/oslo_serialization/tests/test_base64.py new file mode 100644 index 0000000..8d52780 --- /dev/null +++ b/oslo_serialization/tests/test_base64.py @@ -0,0 +1,56 @@ +# Copyright 2015 Red Hat +# 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. + +from oslo_serialization import base64 +from oslotest import base as test_base + + +class Base64Tests(test_base.BaseTestCase): + + def test_encode_as_bytes(self): + self.assertEqual(base64.encode_as_bytes(b'text'), + b'dGV4dA==') + self.assertEqual(base64.encode_as_bytes(u'text'), + b'dGV4dA==') + self.assertEqual(base64.encode_as_bytes(u'e:\xe9'), + b'ZTrDqQ==') + self.assertEqual(base64.encode_as_bytes(u'e:\xe9', encoding='latin1'), + b'ZTrp') + + def test_encode_as_text(self): + self.assertEqual(base64.encode_as_text(b'text'), + u'dGV4dA==') + self.assertEqual(base64.encode_as_text(u'text'), + u'dGV4dA==') + self.assertEqual(base64.encode_as_text(u'e:\xe9'), + u'ZTrDqQ==') + self.assertEqual(base64.encode_as_text(u'e:\xe9', encoding='latin1'), + u'ZTrp') + + def test_decode_as_bytes(self): + self.assertEqual(base64.decode_as_bytes(b'dGV4dA=='), + b'text') + self.assertEqual(base64.decode_as_bytes(u'dGV4dA=='), + b'text') + + def test_decode_as_text(self): + self.assertEqual(base64.decode_as_text(b'dGV4dA=='), + u'text') + self.assertEqual(base64.decode_as_text(u'dGV4dA=='), + u'text') + self.assertEqual(base64.decode_as_text(u'ZTrDqQ=='), + u'e:\xe9') + self.assertEqual(base64.decode_as_text(u'ZTrp', encoding='latin1'), + u'e:\xe9')