diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | pies/__init__.py | 3 | ||||
-rw-r--r-- | pies/_utils.py | 64 | ||||
-rw-r--r-- | pies/ast.py | 10 | ||||
-rw-r--r-- | pies/collections.py | 5 | ||||
-rw-r--r-- | pies/functools.py | 89 | ||||
-rw-r--r-- | pies/overrides.py | 25 | ||||
-rw-r--r-- | pies/unittest.py | 17 | ||||
-rw-r--r-- | pies2overrides/setup.py | 4 | ||||
-rw-r--r-- | setup.py | 8 |
10 files changed, 216 insertions, 15 deletions
@@ -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, @@ -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, |