Got rid of six.

This commit is contained in:
KOLANICH
2022-11-03 14:18:56 +03:00
parent 23aac818e3
commit a23dccef97
13 changed files with 56 additions and 236 deletions

View File

@@ -16,8 +16,6 @@ from enum import EnumMeta
from pyasn1.type.univ import ObjectIdentifier
import six
from cryptography.hazmat.backends import openssl
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers import algorithms
@@ -63,13 +61,8 @@ class FlagEnumMeta(EnumMeta):
return self & other
if six.PY2:
class FlagEnum(IntEnum):
__metaclass__ = FlagEnumMeta
else:
namespace = FlagEnumMeta.__prepare__('FlagEnum', (IntEnum,))
FlagEnum = FlagEnumMeta('FlagEnum', (IntEnum,), namespace)
namespace = FlagEnumMeta.__prepare__('FlagEnum', (IntEnum,))
FlagEnum = FlagEnumMeta('FlagEnum', (IntEnum,), namespace)
@@ -336,9 +329,6 @@ class CompressionAlgorithm(IntEnum):
raise NotImplementedError(self)
def decompress(self, data):
if six.PY2:
data = bytes(data)
if self is CompressionAlgorithm.Uncompressed:
return data

View File

@@ -2,7 +2,6 @@
"""
import contextlib
import functools
import six
import logging
try:
@@ -116,8 +115,7 @@ class KeyAction(object):
"".format(attr=attr, eval=str(expected), got=str(getattr(key, attr))))
def __call__(self, action):
# @functools.wraps(action)
@six.wraps(action)
@functools.wraps(action)
def _action(key, *args, **kwargs):
if key._key is None:
raise PGPError("No key!")

View File

@@ -1,128 +0,0 @@
""" util.py
"""
import six
__all__ = ('memoryview', )
memoryview = memoryview
if six.PY2:
# because Python2's memoryview can't be released directly, nor can it be used as a context manager
# this wrapper object should hopefully make the behavior more uniform to python 3's
import __builtin__
import functools
# this decorator will raise a ValueError if the wrapped memoryview object has been "released"
def notreleased(meth):
@functools.wraps(meth)
def _inner(self, *args, **kwargs):
if self._mem is None:
raise ValueError("operation forbidden on released memoryview object")
return meth(self, *args, **kwargs)
return _inner
class memoryview(object): # flake8: noqa
@property
@notreleased
def obj(self):
"""The underlying object of the memoryview."""
return self._obj
@property
@notreleased
def nbytes(self):
# nbytes == product(shape) * itemsize == len(m.tobytes())
nb = 1
for dim in self.shape:
nb *= dim
return nb * self.itemsize
# TODO: c_contiguous -> (self.ndim == 0 or ???)
# TODO: f_contiguous -> (self.ndim == 0 or ???)
# TODO: contiguous -> return self.c_contiguous or self.f_contiguous
def __new__(cls, obj, parent=None):
memview = object.__new__(cls)
memview._obj = obj if parent is None else parent.obj
return memview
def __init__(self, obj):
if not hasattr(self, '_mem'):
if not isinstance(obj, __builtin__.memoryview):
obj = __builtin__.memoryview(obj)
self._mem = obj
def __dir__(self):
# so dir(...) looks like a memoryview object, and also
# contains our additional methods and properties, but not our instance members
return sorted(set(self.__class__.__dict__) | set(dir(self._mem)))
@notreleased
def __getitem__(self, item):
# if this is a slice, it'll return another real memoryview object
# we'll need to wrap that subview in another memoryview wrapper
if isinstance(item, slice):
return memoryview(self._mem.__getitem__(item))
return self._mem.__getitem__(item)
@notreleased
def __setitem__(self, key, value):
self._mem.__setitem__(key, value)
@notreleased
def __delitem__(self, key):
raise TypeError("cannot delete memory")
def __getattribute__(self, item):
try:
return object.__getattribute__(self, item)
except AttributeError:
if object.__getattribute__(self, '_mem') is None:
raise ValueError("operation forbidden on released memoryview object")
return object.__getattribute__(self, '_mem').__getattribute__(item)
def __setattr__(self, key, value):
if key not in self.__dict__ and hasattr(__builtin__.memoryview, key):
# there are no writable attributes on memoryview objects
# changing indexed values is handled by __setitem__
raise AttributeError("attribute '{}' of 'memoryview' objects is not writable".format(key))
else:
object.__setattr__(self, key, value)
@notreleased
def __len__(self):
return len(self._mem)
def __eq__(self, other):
if isinstance(other, memoryview):
return self._mem == other._mem
return self._mem == other
@notreleased
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.release()
def __repr__(self):
return '<{}memory at 0x{:02X}>'.format('' if self._mem else 'released ', id(self))
def release(self):
"""Release the underlying buffer exposed by the memoryview object"""
# this should effectively do the same job as memoryview.release() in Python 3
self._mem = None
self._obj = None
@notreleased
def hex(self):
"""Return the data in the buffer as a string of hexadecimal numbers."""
return ''.join(('{:02X}'.format(ord(c)) for c in self._mem))
# TODO: cast

View File

@@ -11,8 +11,6 @@ import warnings
from datetime import datetime, timezone
import six
from cryptography.hazmat.primitives import constant_time
from cryptography.hazmat.primitives.asymmetric import padding
@@ -697,7 +695,7 @@ class OnePassSignatureV3(OnePassSignature):
return self._signer
@signer.register(str)
@signer.register(six.text_type)
@signer.register(str)
def signer_str(self, val):
self._signer = val
@@ -719,7 +717,7 @@ class OnePassSignatureV3(OnePassSignature):
_bytes += bytearray([self.sigtype])
_bytes += bytearray([self.halg])
_bytes += bytearray([self.pubalg])
_bytes += binascii.unhexlify(six.b(self.signer))
_bytes += binascii.unhexlify(self.signer.encode("latin-1"))
_bytes += bytearray([int(self.nested)])
return _bytes

View File

@@ -10,8 +10,6 @@ from datetime import datetime
from datetime import timedelta
from datetime import timezone
import six
from .types import EmbeddedSignatureHeader
from .types import Signature
@@ -69,7 +67,7 @@ class URI(Signature):
return self._uri
@uri.register(str)
@uri.register(six.text_type)
@uri.register(str)
def uri_str(self, val):
self._uri = val
@@ -420,7 +418,7 @@ class RegularExpression(Signature):
return self._regex
@regex.register(str)
@regex.register(six.text_type)
@regex.register(str)
def regex_str(self, val):
self._regex = val
@@ -550,7 +548,7 @@ class RevocationKey(Signature):
return self._fingerprint
@fingerprint.register(str)
@fingerprint.register(six.text_type)
@fingerprint.register(str)
@fingerprint.register(Fingerprint)
def fingerprint_str(self, val):
self._fingerprint = Fingerprint(val)
@@ -633,7 +631,7 @@ class NotationData(Signature):
return self._name
@name.register(str)
@name.register(six.text_type)
@name.register(str)
def name_str(self, val):
self._name = val
@@ -646,7 +644,7 @@ class NotationData(Signature):
return self._value
@value.register(str)
@value.register(six.text_type)
@value.register(str)
def value_str(self, val):
self._value = val
@@ -761,7 +759,7 @@ class SignersUserID(Signature):
return self._userid
@userid.register(str)
@userid.register(six.text_type)
@userid.register(str)
def userid_str(self, val):
self._userid = val
@@ -805,7 +803,7 @@ class ReasonForRevocation(Signature):
return self._string
@string.register(str)
@string.register(six.text_type)
@string.register(str)
def string_str(self, val):
self._string = val
@@ -932,7 +930,7 @@ class IssuerFingerprint(Signature):
return self._issuer_fpr
@issuer_fingerprint.register(str)
@issuer_fingerprint.register(six.text_type)
@issuer_fingerprint.register(str)
@issuer_fingerprint.register(Fingerprint)
def issuer_fingerprint_str(self, val):
self._issuer_fpr = Fingerprint(val)
@@ -1004,7 +1002,7 @@ class IntendedRecipient(Signature):
return self._intended_recipient
@intended_recipient.register(str)
@intended_recipient.register(six.text_type)
@intended_recipient.register(str)
@intended_recipient.register(Fingerprint)
def intended_recipient_str(self, val):
self._intended_recipient = Fingerprint(val)

View File

@@ -8,8 +8,6 @@ from ...constants import ImageEncoding
from ...decorators import sdproperty
from ...memoryview import memoryview
__all__ = ('Image',)
@@ -100,7 +98,6 @@ class Image(UserAttribute):
def parse(self, packet):
super(Image, self).parse(packet)
# on Python 2, this will be the wrapper object from memoryview.py
with memoryview(packet) as _head:
_, self.version, self.iencoding, _, _, _ = struct.unpack_from('<hbbiii', _head[:16].tobytes())
del packet[:16]

View File

@@ -5,8 +5,6 @@ from __future__ import division
import abc
import copy
import six
from ..constants import PacketTag
from ..decorators import sdproperty
@@ -149,7 +147,7 @@ class Packet(Dispatchable):
def __init__(self, _=None):
super(Packet, self).__init__()
self.header = self.__headercls__()
if isinstance(self.__typeid__, six.integer_types):
if isinstance(self.__typeid__, int):
self.header.tag = self.__typeid__
@abc.abstractmethod
@@ -176,7 +174,7 @@ class VersionedPacket(Packet):
def __init__(self):
super(VersionedPacket, self).__init__()
if isinstance(self.__ver__, six.integer_types):
if isinstance(self.__ver__, int):
self.header.version = self.__ver__
def __repr__(self):
@@ -237,8 +235,7 @@ class Sub(Key):
# This is required for class MPI to work in both Python 2 and 3
if not six.PY2:
long = int
long = int
class MPI(long):

View File

@@ -18,8 +18,6 @@ import re
import warnings
import weakref
import six
from datetime import datetime, timezone
from cryptography.hazmat.primitives import hashes
@@ -402,7 +400,7 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
def hashdata(self, subject):
_data = bytearray()
if isinstance(subject, six.string_types):
if isinstance(subject, str):
try:
subject = subject.encode('utf-8')
except UnicodeEncodeError:
@@ -858,9 +856,9 @@ class PGPUID(ParentRef):
def __format__(self, format_spec):
if self.is_uid:
comment = six.u("") if self.comment == "" else six.u(" ({:s})").format(self.comment)
email = six.u("") if self.email == "" else six.u(" <{:s}>").format(self.email)
return six.u("{:s}{:s}{:s}").format(self.name, comment, email)
comment = "" if self.comment == "" else " ({:s})".format(self.comment)
email = "" if self.email == "" else " <{:s}>".format(self.email)
return "{:s}{:s}{:s}".format(self.name, comment, email)
raise NotImplementedError
@@ -945,7 +943,7 @@ class PGPMessage(Armorable, PGPObject):
@property
def type(self):
##TODO: it might be better to use an Enum for the output of this
if isinstance(self._message, (six.string_types, six.binary_type, bytearray)):
if isinstance(self._message, (str, bytes, bytearray)):
return 'cleartext'
if isinstance(self._message, LiteralData):
@@ -1043,7 +1041,7 @@ class PGPMessage(Armorable, PGPObject):
self |= pkt
return self
if isinstance(other, (six.string_types, six.binary_type, bytearray)):
if isinstance(other, (str, bytes, bytearray)):
if self._message is None:
self._message = self.text_to_bytes(other)
return self
@@ -1139,7 +1137,7 @@ class PGPMessage(Armorable, PGPObject):
if charset:
msg.charset = charset
# if format in 'tu' and isinstance(message, (six.binary_type, bytearray)):
# if format in 'tu' and isinstance(message, (bytes, bytearray)):
# # if message format is text or unicode and we got binary data, we'll need to transcode it to UTF-8
# message =
@@ -1153,7 +1151,7 @@ class PGPMessage(Armorable, PGPObject):
# if format is None, we can try to detect it
if format is None:
if isinstance(message, six.text_type):
if isinstance(message, str):
# message is definitely UTF-8 already
format = 'u'
@@ -1166,7 +1164,7 @@ class PGPMessage(Armorable, PGPObject):
format = 'b'
# if message is a binary type and we're building a textual message, we need to transcode the bytes to UTF-8
if isinstance(message, (six.binary_type, bytearray)) and (cleartext or format in 'tu'):
if isinstance(message, (bytes, bytearray)) and (cleartext or format in 'tu'):
message = message.decode(charset or 'utf-8')
if cleartext:
@@ -2428,7 +2426,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
sspairs = []
# some type checking
if not isinstance(subject, (type(None), PGPMessage, PGPKey, PGPUID, PGPSignature, six.string_types, bytes, bytearray)):
if not isinstance(subject, (type(None), PGPMessage, PGPKey, PGPUID, PGPSignature, str, bytes, bytearray)):
raise TypeError("Unexpected subject value: {:s}".format(str(type(subject))))
if not isinstance(signature, (type(None), PGPSignature)):
raise TypeError("Unexpected signature value: {:s}".format(str(type(signature))))
@@ -2697,7 +2695,7 @@ class PGPKeyring(collections_abc.Container, collections_abc.Iterable, collection
def __contains__(self, alias):
aliases = set().union(*self._aliases)
if isinstance(alias, six.string_types):
if isinstance(alias, str):
return alias in aliases or alias.replace(' ', '') in aliases
return alias in aliases # pragma: no cover

View File

@@ -1,7 +1,5 @@
""" symenc.py
"""
import six
from cryptography.exceptions import UnsupportedAlgorithm
from cryptography.hazmat.backends import default_backend
@@ -31,7 +29,7 @@ def _encrypt(pt, key, alg, iv=None):
encryptor = Cipher(alg.cipher(key), modes.CFB(iv), default_backend()).encryptor()
except UnsupportedAlgorithm as ex: # pragma: no cover
six.raise_from(PGPEncryptionError, ex)
raise PGPEncryptionError from ex
else:
return bytearray(encryptor.update(pt) + encryptor.finalize())
@@ -52,7 +50,7 @@ def _decrypt(ct, key, alg, iv=None):
decryptor = Cipher(alg.cipher(key), modes.CFB(iv), default_backend()).decryptor()
except UnsupportedAlgorithm as ex: # pragma: no cover
six.raise_from(PGPDecryptionError, ex)
raise PGPDecryptionError from ex
else:
return bytearray(decryptor.update(ct) + decryptor.finalize())

View File

@@ -17,8 +17,6 @@ import weakref
from enum import EnumMeta
from enum import IntEnum
import six
from .decorators import sdproperty
from .errors import PGPError
@@ -36,12 +34,8 @@ __all__ = ['Armorable',
'Fingerprint',
'SorteDeque']
if six.PY2:
FileNotFoundError = IOError
re.ASCII = 0
class Armorable(six.with_metaclass(abc.ABCMeta)):
class Armorable(metaclass=abc.ABCMeta):
__crc24_init = 0x0B704CE
__crc24_poly = 0x1864CFB
@@ -83,7 +77,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
@staticmethod
def is_ascii(text):
if isinstance(text, six.string_types):
if isinstance(text, str):
return bool(re.match(r'^[ -~\r\n\t]*$', text, flags=re.ASCII))
if isinstance(text, (bytes, bytearray)):
@@ -141,7 +135,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
m['body'] = bytearray(base64.b64decode(m['body'].encode()))
except (binascii.Error, TypeError) as ex:
six.raise_from(PGPError(str(ex)), ex)
raise PGPError(str(ex)) from ex
if m['crc'] is not None:
m['crc'] = Header.bytes_to_int(base64.b64decode(m['crc'].encode()))
@@ -163,7 +157,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
crc = Armorable.__crc24_init
if not isinstance(data, bytearray):
data = six.iterbytes(data)
data = iter(data)
for b in data:
crc ^= b << 16
@@ -196,7 +190,7 @@ class Armorable(six.with_metaclass(abc.ABCMeta)):
@classmethod
def from_blob(cls, blob):
obj = cls()
if (not isinstance(blob, six.binary_type)) and (not isinstance(blob, bytearray)):
if (not isinstance(blob, bytes)) and (not isinstance(blob, bytearray)):
po = obj.parse(bytearray(blob, 'latin-1'))
else:
@@ -254,7 +248,7 @@ class ParentRef(object):
self._parent = None
class PGPObject(six.with_metaclass(abc.ABCMeta, object)):
class PGPObject(metaclass=abc.ABCMeta):
@staticmethod
def int_byte_len(i):
@@ -263,16 +257,6 @@ class PGPObject(six.with_metaclass(abc.ABCMeta, object)):
@staticmethod
def bytes_to_int(b, order='big'): # pragma: no cover
"""convert bytes to integer"""
if six.PY2:
# save the original type of b without having to copy any data
_b = b.__class__()
if order != 'little':
b = reversed(b)
if not isinstance(_b, bytearray):
b = six.iterbytes(b)
return sum(c << (i * 8) for i, c in enumerate(b))
return int.from_bytes(b, order)
@@ -281,10 +265,6 @@ class PGPObject(six.with_metaclass(abc.ABCMeta, object)):
"""convert integer to bytes"""
blen = max(minlen, PGPObject.int_byte_len(i), 1)
if six.PY2:
r = iter(_ * 8 for _ in (range(blen) if order == 'little' else range(blen - 1, -1, -1)))
return bytes(bytearray((i >> c) & 0xff for c in r))
return i.to_bytes(blen, order)
@staticmethod
@@ -293,7 +273,7 @@ class PGPObject(six.with_metaclass(abc.ABCMeta, object)):
return text
# if we got bytes, just return it
if isinstance(text, (bytearray, six.binary_type)):
if isinstance(text, (bytearray, bytes)):
return text
# if we were given a unicode string, or if we translated the string into utf-8,
@@ -302,7 +282,7 @@ class PGPObject(six.with_metaclass(abc.ABCMeta, object)):
@staticmethod
def bytes_to_text(text):
if text is None or isinstance(text, six.text_type):
if text is None or isinstance(text, str):
return text
return text.decode('utf-8')
@@ -359,7 +339,7 @@ class Header(Field):
def length_int(self, val):
self._len = val
@length.register(six.binary_type)
@length.register(bytes)
@length.register(bytearray)
def length_bin(self, val):
def _new_len(b):
@@ -536,7 +516,7 @@ class MetaDispatchable(abc.ABCMeta):
nh.parse(packet)
except Exception as ex:
six.raise_from(PGPError(str(ex)), ex)
raise PGPError(str(ex)) from ex
header = nh
@@ -556,7 +536,7 @@ class MetaDispatchable(abc.ABCMeta):
obj.parse(packet)
except Exception as ex:
six.raise_from(PGPError(str(ex)), ex)
raise PGPError(str(ex)) from ex
else:
obj = _makeobj(cls)
@@ -564,7 +544,7 @@ class MetaDispatchable(abc.ABCMeta):
return obj
class Dispatchable(six.with_metaclass(MetaDispatchable, PGPObject)):
class Dispatchable(PGPObject, metaclass=MetaDispatchable):
@abc.abstractproperty
def __headercls__(self): # pragma: no cover
@@ -612,8 +592,7 @@ class SignatureVerification(object):
``sigsubj.subject`` - the subject that was verified using the signature.
"""
for s in [ i for i in self._subjects if i.issues ]:
yield s
yield from [ i for i in self._subjects if i.issues ]
def __init__(self):
"""
@@ -676,7 +655,7 @@ class Fingerprint(str):
if isinstance(other, Fingerprint):
return str(self) == str(other)
if isinstance(other, (six.text_type, bytes, bytearray)):
if isinstance(other, (str, bytes, bytearray)):
if isinstance(other, (bytes, bytearray)): # pragma: no cover
other = other.decode('latin-1')
@@ -694,7 +673,7 @@ class Fingerprint(str):
return hash(str(self))
def __bytes__(self):
return binascii.unhexlify(six.b(self))
return binascii.unhexlify(self.encode("latin-1"))
def __pretty__(self):
content = self
@@ -704,8 +683,8 @@ class Fingerprint(str):
# store in the format: "AAAA BBBB CCCC DDDD EEEE FFFF 0000 1111 2222 3333"
# ^^ note 2 spaces here
spaces = [ ' ' if i != 4 else ' ' for i in range(10) ]
chunks = [ ''.join(g) for g in six.moves.zip_longest(*[iter(content)] * 4) ]
content = ''.join(j for i in six.moves.zip_longest(chunks, spaces, fillvalue='') for j in i).strip()
chunks = [ ''.join(g) for g in itertools.zip_longest(*[iter(content)] * 4) ]
content = ''.join(j for i in itertools.zip_longest(chunks, spaces, fillvalue='') for j in i).strip()
return content
def __repr__(self):

View File

@@ -44,7 +44,6 @@ packages =
install_requires =
cryptography>=2.6,<38
pyasn1
six>=1.9.0
python_requires = >=3.5
# doc_requires =

View File

@@ -3,7 +3,6 @@
import pytest
import glob
import os
import six
from pgpy import PGPKey
from pgpy import PGPKeyring
@@ -41,22 +40,22 @@ class TestPGPMessage(object):
@pytest.fixture
def un():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'))
return PGPUID.new('Temperair\xe9e Youx\'seur')
@pytest.fixture
def unc():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), comment=six.u('\u2603'))
return PGPUID.new('Temperair\xe9e Youx\'seur', comment='\u2603')
@pytest.fixture
def une():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), email='snowman@not.an.email.addre.ss')
return PGPUID.new('Temperair\xe9e Youx\'seur', email='snowman@not.an.email.addre.ss')
@pytest.fixture
def unce():
return PGPUID.new(six.u('Temperair\xe9e Youx\'seur'), comment=six.u('\u2603'), email='snowman@not.an.email.addre.ss')
return PGPUID.new('Temperair\xe9e Youx\'seur', comment='\u2603', email='snowman@not.an.email.addre.ss')
@pytest.fixture
@@ -81,10 +80,10 @@ class TestPGPUID(object):
assert abe_image.image == abebytes
def test_format(self, un, unc, une, unce):
assert six.u("{:s}").format(un) == six.u('Temperair\xe9e Youx\'seur')
assert six.u("{:s}").format(unc) == six.u('Temperair\xe9e Youx\'seur (\u2603)')
assert six.u("{:s}").format(une) == six.u('Temperair\xe9e Youx\'seur <snowman@not.an.email.addre.ss>')
assert six.u("{:s}").format(unce) == six.u('Temperair\xe9e Youx\'seur (\u2603) <snowman@not.an.email.addre.ss>')
assert "{:s}".format(un) == 'Temperair\xe9e Youx\'seur'
assert "{:s}".format(unc) == 'Temperair\xe9e Youx\'seur (\u2603)'
assert "{:s}".format(une) == 'Temperair\xe9e Youx\'seur <snowman@not.an.email.addre.ss>'
assert "{:s}".format(unce) == 'Temperair\xe9e Youx\'seur (\u2603) <snowman@not.an.email.addre.ss>'
_keyfiles = sorted(glob.glob('tests/testdata/blocks/*key*.asc'))
@@ -211,10 +210,8 @@ class TestPGPKeyring(object):
def test_select_fingerprint(self, keyring):
for fp, name in [("F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36", "RSA von TestKey"),
(six.u("F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36"), six.u("RSA von TestKey")),
(Fingerprint("F429 4BC8 094A 7E05 85C8 5E86 3747 3B37 58C4 4F36"), "RSA von TestKey"),
("EBC8 8A94 ACB1 10F1 BE3F E3C1 2B47 4BB0 2084 C712", "DSA von TestKey"),
(six.u("EBC8 8A94 ACB1 10F1 BE3F E3C1 2B47 4BB0 2084 C712"), six.u("DSA von TestKey")),
(Fingerprint("EBC8 8A94 ACB1 10F1 BE3F E3C1 2B47 4BB0 2084 C712"), "DSA von TestKey"),]:
with keyring.key(fp) as key:
assert key.fingerprint == fp

View File

@@ -7,7 +7,6 @@ import copy
import glob
import inspect
import os.path
import six
import pgpy
@@ -55,7 +54,7 @@ def check_id(obj):
return False
# these types are immutable
if isinstance(obj, (six.string_types, datetime)):
if isinstance(obj, (str, datetime)):
return False
# integers are kind of a special case.