summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Crosley <timothy.crosley@gmail.com>2013-12-14 23:32:38 -0500
committerTimothy Crosley <timothy.crosley@gmail.com>2013-12-14 23:32:38 -0500
commit462014b7e0e3e3976530ed1329fd034dc78f40f8 (patch)
tree7272275ba8382ea9de0b2b01a912237c828a450a
parent6ab8edafabb2ff47cce842f81ea7f6a29ead8a00 (diff)
parent7bb7adb937e6e443958e67c62cc1f5cc60c14c57 (diff)
downloadpies-462014b7e0e3e3976530ed1329fd034dc78f40f8.tar.gz
Merge branch 'release/2.5.0'2.5.0
-rw-r--r--README.md6
-rw-r--r--pies/__init__.py3
-rw-r--r--pies/_utils.py64
-rw-r--r--pies/ast.py10
-rw-r--r--pies/collections.py5
-rw-r--r--pies/functools.py89
-rw-r--r--pies/overrides.py25
-rw-r--r--pies/unittest.py17
-rw-r--r--pies2overrides/setup.py4
-rw-r--r--setup.py8
10 files changed, 216 insertions, 15 deletions
diff --git a/README.md b/README.md
index d86f4e2..c694cea 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
====================
[![PyPI version](https://badge.fury.io/py/pies.png)](http://badge.fury.io/py/pies)
[![PyPi downloads](https://pypip.in/d/pies/badge.png)](https://crate.io/packages/pies/)
+[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/timothycrosley/isort/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
The simplest (and tastiest) way to write one program that runs on both Python 2.6+ and Python 3.
@@ -63,6 +64,7 @@ Functions:
Types:
+- object (__str__ automatically has correct behavior on all versions of Python)
- chr (creates a unichr object in Python2)
- str (creates a unicode object in Python2)
- dict (creating a dict using dict() will give you all the special Python3 itemview results, but using {} will not)
@@ -106,6 +108,7 @@ Full List:
- pickle
- StringIO
- sys
+- unittest
Special Syntax (The Ugly)
======================
@@ -120,6 +123,7 @@ Sadly, there is still special syntax that is present for corner cases.
- keysview(collection) - should replace collection.keys() where you do not control the collection passed in
- execute() - enables Python 3 style exec statements on both environments.
- integer_types - may want to use isinstance(variable, integer_types) instead of type(variable, int) as long values will not match int in Python2.
+- NewClass(with_metaclass(metaclass, parent_class)) - Should replace both "__metaclass__ = metaclass" and "NewClass(metaclass=metaclass)" as a way to assign meta-classes.
What Could be Improved?
======================
@@ -131,4 +135,4 @@ or email me at timothy.crosley@gmail.com.
Thanks and I hope you enjoy pies!
-~Timothy
+~Timothy Crosley
diff --git a/pies/__init__.py b/pies/__init__.py
index d083f27..313d8e0 100644
--- a/pies/__init__.py
+++ b/pies/__init__.py
@@ -29,5 +29,4 @@
OTHER DEALINGS IN THE SOFTWARE.
"""
-
-from __future__ import absolute_import
+__version__ = "2.5.0"
diff --git a/pies/_utils.py b/pies/_utils.py
new file mode 100644
index 0000000..f8937ae
--- /dev/null
+++ b/pies/_utils.py
@@ -0,0 +1,64 @@
+"""
+ pies/_utils.py
+
+ Utils internal to the pies library and not meant for direct external usage.
+
+ 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 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.
+"""
+
+
+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):
+ 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 isinstance(instance, bases)
+
+ return with_metaclass(UnmodifiedIsInstance, *bases)
diff --git a/pies/ast.py b/pies/ast.py
new file mode 100644
index 0000000..43739a1
--- /dev/null
+++ b/pies/ast.py
@@ -0,0 +1,10 @@
+from __future__ import absolute_import
+
+from ast import *
+
+from .version_info import PY2
+
+if PY2:
+ Try = TryExcept
+else:
+ TryFinally = ()
diff --git a/pies/collections.py b/pies/collections.py
index 83e4611..a8a94cf 100644
--- a/pies/collections.py
+++ b/pies/collections.py
@@ -7,4 +7,7 @@ from .version_info import PY2
if PY2:
from UserString import *
from UserList import *
- from ordereddict import OrderedDict
+
+ import sys
+ if sys.version_info < (2, 7):
+ from ordereddict import OrderedDict
diff --git a/pies/functools.py b/pies/functools.py
index 29749cf..2f2c157 100644
--- a/pies/functools.py
+++ b/pies/functools.py
@@ -1,8 +1,97 @@
from __future__ import absolute_import
+import sys
from functools import *
from .version_info import PY2
if PY2:
reduce = reduce
+
+if sys.version_info < (3, 2):
+ try:
+ from threading import Lock
+ except ImportError:
+ from dummy_threading import Lock
+
+ from .collections import OrderedDict
+
+ 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
diff --git a/pies/overrides.py b/pies/overrides.py
index 926d51b..5577e59 100644
--- a/pies/overrides.py
+++ b/pies/overrides.py
@@ -24,10 +24,9 @@ from __future__ import absolute_import
import functools
from numbers import Integral
+from ._utils import unmodified_isinstance, with_metaclass
from .version_info import PY2, PY3, VERSION
-__version__ = "2.0.1"
-
native_dict = dict
native_round = round
native_filter = filter
@@ -38,10 +37,11 @@ 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_next', 'native_object', 'with_metaclass']
if PY3:
import urllib
@@ -161,7 +161,7 @@ else:
def keysview(collection):
return dict_keys(collection)
- class dict(native_dict):
+ class dict(unmodified_isinstance(native_dict)):
def has_key(self, *args, **kwargs):
return AttributeError("'dict' object has no attribute 'has_key'")
@@ -198,5 +198,20 @@ else:
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)
+
+ 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']
+ 'input', 'range', 'filter', 'map', 'zip', 'object']
diff --git a/pies/unittest.py b/pies/unittest.py
new file mode 100644
index 0000000..a5d0960
--- /dev/null
+++ b/pies/unittest.py
@@ -0,0 +1,17 @@
+from __future__ import absolute_import
+
+import sys
+from unittest import *
+
+from ._utils import unmodified_isinstance
+
+NativeTestCase = TestCase
+
+if sys.version_info < (2, 7):
+ skip = lambda why: (lambda func: 'skip')
+ skipIf = lambda cond, why: (skip(why) if cond else lambda func: func)
+
+ class TestCase(unmodified_isinstance(TestCase)):
+ def assertIs(self, expr1, expr2, msg=None):
+ if expr1 is not expr2:
+ self.fail(msg or '%r is not %r' % (expr1, expr2))
diff --git a/pies2overrides/setup.py b/pies2overrides/setup.py
index 45881a0..5cf5aff 100644
--- a/pies2overrides/setup.py
+++ b/pies2overrides/setup.py
@@ -14,12 +14,12 @@ if sys.version_info[0] == 2 and sys.version_info[1] < 7:
install_requires += ['ordereddict', 'argparse']
setup(name='pies2overrides',
- version='2.0.1',
+ version='2.5.0',
description='Defines override classes that should be included with pies only if running on Python2.',
author='Timothy Crosley',
author_email='timothy.crosley@gmail.com',
url='https://github.com/timothycrosley/pies',
- download_url='https://github.com/timothycrosley/pies/blob/master/pies2overrides/dist/pies2overrides-2.0.1.tar.gz?raw=true',
+ download_url='https://github.com/timothycrosley/pies/blob/master/pies2overrides/dist/pies2overrides-2.5.0.tar.gz?raw=true',
license="MIT",
install_requires=install_requires,
requires=install_requires,
diff --git a/setup.py b/setup.py
index c537514..d6f85ef 100644
--- a/setup.py
+++ b/setup.py
@@ -20,17 +20,17 @@ if sys.version_info[0] < 3 or sys.version_info[1] < 4:
try:
import pypandoc
readme = pypandoc.convert('README.md', 'rst')
-except (IOError, ImportError):
+except (IOError, ImportError, OSError, RuntimeError):
readme = ''
setup(name='pies',
- version='2.0.1',
+ version='2.5.0',
description='The simplest way to write one program that runs on both Python 2 and Python 3.',
- long_readme=readme,
+ long_description=readme,
author='Timothy Crosley',
author_email='timothy.crosley@gmail.com',
url='https://github.com/timothycrosley/pies',
- download_url='https://github.com/timothycrosley/pies/blob/master/dist/pies-2.0.1.tar.gz?raw=true',
+ download_url='https://github.com/timothycrosley/pies/blob/master/dist/pies-2.5.0.tar.gz?raw=true',
license="MIT",
install_requires=install_requires,
requires=install_requires,