Repository: timothycrosley/isort
Pull request #285 (2015-07-13 03:14:49)

Feature/no deps


Patch:
diff --git a/isort/isort.py b/isort/isort.py
index b838b3e4..807bac02 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -36,8 +36,8 @@ from difflib import unified_diff
 from sys import path as PYTHONPATH
 from sys import stderr, stdout
 
-from natsort import natsorted
-from pies.overrides import *
+from .natural import nsorted
+from .pie_slice import *
 
 from . import settings
 
@@ -322,7 +322,7 @@ class SortImports(object):
 
             import_start = "from {0} import ".format(module)
             from_imports = list(self.imports[section]['from'][module])
-            from_imports = natsorted(from_imports, key=lambda key: self._module_key(key, self.config, True))
+            from_imports = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True))
             if self.remove_imports:
                 from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
                                 self.remove_imports]
@@ -427,9 +427,9 @@ class SortImports(object):
         output = []
         for section in itertools.chain(SECTIONS, self.config['forced_separate']):
             straight_modules = list(self.imports[section]['straight'])
-            straight_modules = natsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
+            straight_modules = nsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
             from_modules = sorted(list(self.imports[section]['from'].keys()))
-            from_modules = natsorted(from_modules, key=lambda key: self._module_key(key, self.config, ))
+            from_modules = nsorted(from_modules, key=lambda key: self._module_key(key, self.config, ))
 
             section_output = []
             if self.config.get('from_first', False):
diff --git a/isort/main.py b/isort/main.py
index c2b42cf2..6e3188aa 100755
--- a/isort/main.py
+++ b/isort/main.py
@@ -26,7 +26,7 @@ import os
 import sys
 
 import setuptools
-from pies.overrides import *
+from .pie_slice import *
 
 from isort import SECTION_NAMES, SortImports, __version__
 from isort.settings import default, from_path
diff --git a/isort/natural.py b/isort/natural.py
new file mode 100644
index 00000000..a20ba1d4
--- /dev/null
+++ b/isort/natural.py
@@ -0,0 +1,46 @@
+"""isort/natural.py.
+
+Enables sorting strings that contain numbers naturally
+
+usage:
+    natural.nsorted(list)
+
+Copyright (C) 2013  Timothy Edmund Crosley
+
+Implementation originally from @HappyLeapSecond stack overflow user in response to:
+   http://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+import re
+
+
+def _atoi(text):
+    return int(text) if text.isdigit() else text
+
+
+def _natural_keys(text):
+    return [_atoi(c) for c in re.split('(\d+)', text)]
+
+
+def nsorted(to_sort, key=None):
+    """Returns a naturally sorted list"""
+    if not key:
+        key_callback = _natural_keys
+    else:
+        key_callback = lambda item: _natural_keys(key(item))
+
+    return sorted(to_sort, key=key_callback)
diff --git a/isort/pie_slice.py b/isort/pie_slice.py
new file mode 100644
index 00000000..5cef39b1
--- /dev/null
+++ b/isort/pie_slice.py
@@ -0,0 +1,528 @@
+"""pie_slice/overrides.py.
+
+Overrides Python syntax to conform to the Python3 version as much as possible using a '*' import
+
+Copyright (C) 2013  Timothy Edmund Crosley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copie_slice of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copie_slice or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+from __future__ import absolute_import
+
+import abc
+import functools
+import sys
+from numbers import Integral
+
+__version__ = "1.1.0"
+
+PY2 = sys.version_info[0] == 2
+PY3 = sys.version_info[0] == 3
+VERSION = sys.version_info
+
+native_dict = dict
+native_round = round
+native_filter = filter
+native_map = map
+native_zip = zip
+native_range = range
+native_str = str
+native_chr = chr
+native_input = input
+native_next = next
+native_object = object
+
+common = ['native_dict', 'native_round', 'native_filter', 'native_map', 'native_range', 'native_str', 'native_chr',
+          'native_input', 'PY2', 'PY3', 'u', 'itemsview', 'valuesview', 'keysview', 'execute', 'integer_types',
+          'native_next', 'native_object', 'with_metaclass', 'OrderedDict', 'lru_cache']
+
+
+def with_metaclass(meta, *bases):
+    """Enables use of meta classes across Python Versions. taken from jinja2/_compat.py.
+
+    Use it like this::
+
+        class BaseForm(object):
+            pass
+
+        class FormType(type):
+            pass
+
+        class Form(with_metaclass(FormType, BaseForm)):
+            pass
+
+    """
+    class metaclass(meta):
+        __call__ = type.__call__
+        __init__ = type.__init__
+        def __new__(cls, name, this_bases, d):
+            if this_bases is None:
+                return type.__new__(cls, name, (), d)
+            return meta(name, bases, d)
+    return metaclass('temporary_class', None, {})
+
+
+def unmodified_isinstance(*bases):
+    """When called in the form
+
+    MyOverrideClass(unmodified_isinstance(BuiltInClass))
+
+    it allows calls against passed in built in instances to pass even if there not a subclass
+
+    """
+    class UnmodifiedIsInstance(type):
+        if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
+
+            @classmethod
+            def __instancecheck__(cls, instance):
+                if cls.__name__ in (str(base.__name__) for base in bases):
+                    return isinstance(instance, bases)
+
+                subclass = getattr(instance, '__class__', None)
+                subtype = type(instance)
+                instance_type = getattr(abc, '_InstanceType', None)
+                if not instance_type:
+                    class test_object:
+                        pass
+                    instance_type = type(test_object)
+                if subtype is instance_type:
+                    subtype = subclass
+                if subtype is subclass or subclass is None:
+                    return cls.__subclasscheck__(subtype)
+                return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype))
+        else:
+            @classmethod
+            def __instancecheck__(cls, instance):
+                if cls.__name__ in (str(base.__name__) for base in bases):
+                    return isinstance(instance, bases)
+
+                return type.__instancecheck__(cls, instance)
+
+    return with_metaclass(UnmodifiedIsInstance, *bases)
+
+
+if PY3:
+    import urllib
+    import builtins
+    from urllib import parse
+
+    integer_types = (int, )
+
+    def u(string):
+        return string
+
+    def itemsview(collection):
+        return collection.items()
+
+    def valuesview(collection):
+        return collection.values()
+
+    def keysview(collection):
+        return collection.keys()
+
+    urllib.quote = parse.quote
+    urllib.quote_plus = parse.quote_plus
+    urllib.unquote = parse.unquote
+    urllib.unquote_plus = parse.unquote_plus
+    urllib.urlencode = parse.urlencode
+    execute = getattr(builtins, 'exec')
+    if VERSION[1] < 2:
+        def callable(entity):
+            return hasattr(entity, '__call__')
+        common.append('callable')
+
+    __all__ = common + ['urllib']
+else:
+    from itertools import ifilter as filter
+    from itertools import imap as map
+    from itertools import izip as zip
+    from decimal import Decimal, ROUND_HALF_EVEN
+
+    import codecs
+    str = unicode
+    chr = unichr
+    input = raw_input
+    range = xrange
+    integer_types = (int, long)
+
+    import sys
+    stdout = sys.stdout
+    stderr = sys.stderr
+    reload(sys)
+    sys.stdout = stdout
+    sys.stderr = stderr
+    sys.setdefaultencoding('utf-8')
+
+    def _create_not_allowed(name):
+        def _not_allow(*args, **kwargs):
+            raise NameError("name '{0}' is not defined".format(name))
+        _not_allow.__name__ = name
+        return _not_allow
+
+    for removed in ('apply', 'cmp', 'coerce', 'execfile', 'raw_input', 'unpacks'):
+        globals()[removed] = _create_not_allowed(removed)
+
+    def u(s):
+        if isinstance(s, unicode):
+            return s
+        else:
+            return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+
+    def execute(_code_, _globs_=None, _locs_=None):
+        """Execute code in a namespace."""
+        if _globs_ is None:
+            frame = sys._getframe(1)
+            _globs_ = frame.f_globals
+            if _locs_ is None:
+                _locs_ = frame.f_locals
+            del frame
+        elif _locs_ is None:
+            _locs_ = _globs_
+        exec("""exec _code_ in _globs_, _locs_""")
+
+    class _dict_view_base(object):
+        __slots__ = ('_dictionary', )
+
+        def __init__(self, dictionary):
+            self._dictionary = dictionary
+
+        def __repr__(self):
+            return "{0}({1})".format(self.__class__.__name__, str(list(self.__iter__())))
+
+        def __unicode__(self):
+            return str(self.__repr__())
+
+        def __str__(self):
+            return str(self.__unicode__())
+
+    class dict_keys(_dict_view_base):
+        __slots__ = ()
+
+        def __iter__(self):
+            return self._dictionary.iterkeys()
+
+    class dict_values(_dict_view_base):
+        __slots__ = ()
+
+        def __iter__(self):
+            return self._dictionary.itervalues()
+
+    class dict_items(_dict_view_base):
+        __slots__ = ()
+
+        def __iter__(self):
+            return self._dictionary.iteritems()
+
+    def itemsview(collection):
+        return dict_items(collection)
+
+    def valuesview(collection):
+        return dict_values(collection)
+
+    def keysview(collection):
+        return dict_keys(collection)
+
+    class dict(unmodified_isinstance(native_dict)):
+        def has_key(self, *args, **kwargs):
+            return AttributeError("'dict' object has no attribute 'has_key'")
+
+        def items(self):
+            return dict_items(self)
+
+        def keys(self):
+            return dict_keys(self)
+
+        def values(self):
+            return dict_values(self)
+
+    def round(number, ndigits=None):
+        return_int = False
+        if ndigits is None:
+            return_int = True
+            ndigits = 0
+        if hasattr(number, '__round__'):
+            return number.__round__(ndigits)
+
+        if ndigits < 0:
+            raise NotImplementedError('negative ndigits not supported yet')
+        exponent = Decimal('10') ** (-ndigits)
+        d = Decimal.from_float(number).quantize(exponent,
+                                                rounding=ROUND_HALF_EVEN)
+        if return_int:
+            return int(d)
+        else:
+            return float(d)
+
+    def next(iterator):
+        try:
+            iterator.__next__()
+        except Exception:
+            native_next(iterator)
+
+    class FixStr(type):
+        def __new__(cls, name, bases, dct):
+            if '__str__' in dct:
+                dct['__unicode__'] = dct['__str__']
+            dct['__str__'] = lambda self: self.__unicode__().encode('utf-8')
+            return type.__new__(cls, name, bases, dct)
+
+        if sys.version_info[1] <= 6:
+            def __instancecheck__(cls, instance):
+                if cls.__name__ == "object":
+                    return isinstance(instance, native_object)
+
+                subclass = getattr(instance, '__class__', None)
+                subtype = type(instance)
+                instance_type = getattr(abc, '_InstanceType', None)
+                if not instance_type:
+                    class test_object:
+                        pass
+                    instance_type = type(test_object)
+                if subtype is instance_type:
+                    subtype = subclass
+                if subtype is subclass or subclass is None:
+                    return cls.__subclasscheck__(subtype)
+                return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype))
+        else:
+            def __instancecheck__(cls, instance):
+                if cls.__name__ == "object":
+                    return isinstance(instance, native_object)
+                return type.__instancecheck__(cls, instance)
+
+    class object(with_metaclass(FixStr, object)):
+        pass
+
+    __all__ = common + ['round', 'dict', 'apply', 'cmp', 'coerce', 'execfile', 'raw_input', 'unpacks', 'str', 'chr',
+                        'input', 'range', 'filter', 'map', 'zip', 'object']
+
+if sys.version_info[0] == 2 and sys.version_info[1] < 7:
+    # OrderedDict
+    # Copyright (c) 2009 Raymond Hettinger
+    #
+    # Permission is hereby granted, free of charge, to any person
+    # obtaining a copy of this software and associated documentation files
+    # (the "Software"), to deal in the Software without restriction,
+    # including without limitation the rights to use, copy, modify, merge,
+    # publish, distribute, sublicense, and/or sell copies of the Software,
+    # and to permit persons to whom the Software is furnished to do so,
+    # subject to the following conditions:
+    #
+    #     The above copyright notice and this permission notice shall be
+    #     included in all copies or substantial portions of the Software.
+    #
+    #     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    #     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+    #     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    #     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+    #     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+    #     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    #     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+    #     OTHER DEALINGS IN THE SOFTWARE.
+
+    from UserDict import DictMixin
+
+    class OrderedDict(dict, DictMixin):
+
+        def __init__(self, *args, **kwds):
+            if len(args) > 1:
+                raise TypeError('expected at most 1 arguments, got %d' % len(args))
+            try:
+                self.__end
+            except AttributeError:
+                self.clear()
+            self.update(*args, **kwds)
+
+        def clear(self):
+            self.__end = end = []
+            end += [None, end, end]         # sentinel node for doubly linked list
+            self.__map = {}                 # key --> [key, prev, next]
+            dict.clear(self)
+
+        def __setitem__(self, key, value):
+            if key not in self:
+                end = self.__end
+                curr = end[1]
+                curr[2] = end[1] = self.__map[key] = [key, curr, end]
+            dict.__setitem__(self, key, value)
+
+        def __delitem__(self, key):
+            dict.__delitem__(self, key)
+            key, prev, next = self.__map.pop(key)
+            prev[2] = next
+            next[1] = prev
+
+        def __iter__(self):
+            end = self.__end
+            curr = end[2]
+            while curr is not end:
+                yield curr[0]
+                curr = curr[2]
+
+        def __reversed__(self):
+            end = self.__end
+            curr = end[1]
+            while curr is not end:
+                yield curr[0]
+                curr = curr[1]
+
+        def popitem(self, last=True):
+            if not self:
+                raise KeyError('dictionary is empty')
+            if last:
+                key = reversed(self).next()
+            else:
+                key = iter(self).next()
+            value = self.pop(key)
+            return key, value
+
+        def __reduce__(self):
+            items = [[k, self[k]] for k in self]
+            tmp = self.__map, self.__end
+            del self.__map, self.__end
+            inst_dict = vars(self).copy()
+            self.__map, self.__end = tmp
+            if inst_dict:
+                return (self.__class__, (items,), inst_dict)
+            return self.__class__, (items,)
+
+        def keys(self):
+            return list(self)
+
+        setdefault = DictMixin.setdefault
+        update = DictMixin.update
+        pop = DictMixin.pop
+        values = DictMixin.values
+        items = DictMixin.items
+        iterkeys = DictMixin.iterkeys
+        itervalues = DictMixin.itervalues
+        iteritems = DictMixin.iteritems
+
+        def __repr__(self):
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+
+        def copy(self):
+            return self.__class__(self)
+
+        @classmethod
+        def fromkeys(cls, iterable, value=None):
+            d = cls()
+            for key in iterable:
+                d[key] = value
+            return d
+
+        def __eq__(self, other):
+            if isinstance(other, OrderedDict):
+                if len(self) != len(other):
+                    return False
+                for p, q in  zip(self.items(), other.items()):
+                    if p != q:
+                        return False
+                return True
+            return dict.__eq__(self, other)
+
+        def __ne__(self, other):
+            return not self == other
+else:
+    from collections import OrderedDict
+
+
+if sys.version_info < (3, 2):
+    try:
+        from threading import Lock
+    except ImportError:
+        from dummy_threading import Lock
+
+    from functools import wraps
+
+    def lru_cache(maxsize=100):
+        """Least-recently-used cache decorator.
+        Taking from: https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py
+        with slight modifications.
+        If *maxsize* is set to None, the LRU features are disabled and the cache
+        can grow without bound.
+        Arguments to the cached function must be hashable.
+        View the cache statistics named tuple (hits, misses, maxsize, currsize) with
+        f.cache_info().  Clear the cache and statistics with f.cache_clear().
+        Access the underlying function with f.__wrapped__.
+        See:  http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+
+        """
+        def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
+            hits, misses = [0], [0]
+            kwd_mark = (object(),)          # separates positional and keyword args
+            lock = Lock()
+
+            if maxsize is None:
+                CACHE = dict()
+
+                @wraps(user_function)
+                def wrapper(*args, **kwds):
+                    key = args
+                    if kwds:
+                        key += kwd_mark + tuple(sorted(kwds.items()))
+                    try:
+                        result = CACHE[key]
+                        hits[0] += 1
+                        return result
+                    except KeyError:
+                        pass
+                    result = user_function(*args, **kwds)
+                    CACHE[key] = result
+                    misses[0] += 1
+                    return result
+            else:
+                CACHE = OrderedDict()
+
+                @wraps(user_function)
+                def wrapper(*args, **kwds):
+                    key = args
+                    if kwds:
+                        key += kwd_mark + tuple(sorted(kwds.items()))
+                    with lock:
+                        cached = CACHE.get(key, None)
+                        if cached:
+                            del CACHE[key]
+                            CACHE[key] = cached
+                            hits[0] += 1
+                            return cached
+                    result = user_function(*args, **kwds)
+                    with lock:
+                        CACHE[key] = result     # record recent use of this key
+                        misses[0] += 1
+                        while len(CACHE) > maxsize:
+                            CACHE.popitem(last=False)
+                    return result
+
+            def cache_info():
+                """Report CACHE statistics."""
+                with lock:
+                    return _CacheInfo(hits[0], misses[0], maxsize, len(CACHE))
+
+            def cache_clear():
+                """Clear the CACHE and CACHE statistics."""
+                with lock:
+                    CACHE.clear()
+                    hits[0] = misses[0] = 0
+
+            wrapper.cache_info = cache_info
+            wrapper.cache_clear = cache_clear
+            return wrapper
+
+        return decorating_function
+
+else:
+    from functools import lru_cache
diff --git a/isort/settings.py b/isort/settings.py
index 8e44611a..f23a5cc3 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -27,8 +27,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
 import os
 from collections import namedtuple
 
-from pies.functools import lru_cache
-from pies.overrides import *
+from .pie_slice import *
 
 try:
     import configparser
diff --git a/setup.py b/setup.py
index f51220f7..6daad883 100755
--- a/setup.py
+++ b/setup.py
@@ -57,8 +57,6 @@ setup(name='isort',
         'distutils.commands': ['isort = isort.main:ISortCommand'],
       },
       packages=['isort'],
-      requires=['pies', 'natsort'],
-      install_requires=['pies>=2.6.2', 'natsort>=3.0.0'],
       cmdclass={'test': PyTest},
       keywords='Refactor, Python, Python2, Python3, Refactoring, Imports, Sort, Clean',
       classifiers=['Development Status :: 6 - Mature',
@@ -76,6 +74,7 @@ setup(name='isort',
                    'Programming Language :: Python :: 3.2',
                    'Programming Language :: Python :: 3.3',
                    'Programming Language :: Python :: 3.4',
+                   'Programming Language :: Python :: 3.5',
                    'Topic :: Software Development :: Libraries',
                    'Topic :: Utilities'],
       **PyTest.extra_kwargs)


Test patch:
diff --git a/test_isort.py b/test_isort.py
index d8467498..cd9e5b90 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -22,7 +22,7 @@ OTHER DEALINGS IN THE SOFTWARE.
 """
 from __future__ import absolute_import, division, print_function, unicode_literals
 
-from pies.overrides import *
+from isort.pie_slice import *
 
 from isort.isort import SortImports
 from isort.settings import WrapModes