nine-1.1.0/0000755000175000017510000000000013611655256012027 5ustar nannan00000000000000nine-1.1.0/LICENSE.rst0000644000175000017510000000037713611646504013646 0ustar nannan00000000000000I, the copyright holder of this work, hereby release it into the public domain. This applies worldwide. In case this is not legally possible, I grant any entity the right to use, modify and redistribute this work for any purpose, without any conditions. nine-1.1.0/MANIFEST.in0000644000175000017510000000022513611646504013560 0ustar nannan00000000000000# http://docs.python.org/3/distutils/sourcedist.html#specifying-the-files-to-distribute include *.rst recursive-include nine * global-exclude *.pyc nine-1.1.0/PKG-INFO0000644000175000017510000002343113611655256013127 0ustar nannan00000000000000Metadata-Version: 1.1 Name: nine Version: 1.1.0 Summary: Python 2 / 3 compatibility, like six, but favouring Python 3 Home-page: https://github.com/nandoflorestan/nine Author: Nando Florestan Author-email: nandoflorestan@gmail.com License: Public domain Description: Let's write Python 3 right now! =============================== When the best Python 2/Python 3 compatibility modules -- especially the famous `*six* library invented by Benjamin Peterson `_ -- were created, they were written from the point of view of a Python 2 programmer starting to grok Python 3. If you use *six*, your code is compatible, but stuck in Python 2 idioms. **nine** turns **six** upside down. You write your code using Python 3 idioms -- as much as possible --, and it is the Python 2 "version" that is patched. Needless to say, this approach is more future-proof. When thou writeth Python, thou shalt write Python 3 and, just for a little longer, ensure that the thing worketh on Python 2.7. *nine* facilitates this point of view. You can write code that is as 3ish as possible while still supporting 2.6. For instance, you don't type ``unicode`` anymore, you type ``str``, and *nine* makes ``str`` point to ``unicode`` on Python 2 (if you use our boilerplate). Also, ``map``, ``zip`` and ``filter`` have Python 3 behaviour, on Python 2, meaning they return iterators, not lists. Honestly you should not spend one thought on Python 2.6 anymore, it is `no longer supported `_ since its final release (2.6.9) in October 2013. Nobody uses 3.0 or 3.1 either. Python 2.7 has finally met its demise on the first day of 2020. *nine* is extremely stable and unlikely to change since it solves an old problem that never changes. Nobody should be surprised if *nine* isn't updated for months or even years. The author(s) of *nine* donate this module to the public domain. To understand most of the intricacies involved in achieving 2&3 compatibility in a single codebase, I recommend reading this: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ Using nine ========== In each of your modules, start by declaring a text encoding and importing Python 3 behaviours from __future__. Then import variables from *nine*, as per this boilerplate:: # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) from nine import (IS_PYTHON2, str, basestring, native_str, chr, long, integer_types, class_types, range, range_list, reraise, iterkeys, itervalues, iteritems, map, zip, filter, input, implements_iterator, implements_to_string, implements_repr, nine, nimport) I know that is ugly. What did you expect? *nine* is 3 squared. OK, in many cases you can get away with less:: # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) from nine import IS_PYTHON2, nimport, nine, range, str, basestring But in the second case you need to remember to import the missing stuff when you use it, and it is not realistic to expect that you will remember, is it? Unicode ======= Because of the ``unicode_literals`` import, **all string literals in the module become unicode objects**. No need to add a "u" prefix to each string literal. This is the saner approach since in Python 3 strings are unicode objects by default, and you can then indicate ``b"this is a byte string literal"``. The literals that actually need to be byte strings are very rare. But you wouldn't believe how many developers are irrationally afraid of taking this simple step... If you don't know much about Unicode, just read `The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) `_ Importing moved stuff ===================== Many standard library modules were renamed in Python 3, but nine can help. The ``nimport`` function gets the new Python 3 name, but knows to import the old name if running in Python 2. For instance, instead of writing this to import pickle:: # Bad: try: import cPickle as pickle # Python 2.x except ImportError: import pickle # Python 3 automatically uses the C version. ...you can write this:: # Good: pickle = nimport('pickle') For variables that have been moved: In the argument, please separate the module from the variable with a colon:: name2codepoint = nimport('html.entities:name2codepoint') Want StringIO? I recommend you build lists instead. But if you really need it:: # Good: if IS_PYTHON2: from cStringIO import StringIO as BytesIO, StringIO NativeStringIO = BytesIO else: from io import BytesIO, StringIO NativeStringIO = StringIO Our coverage of Python version differences probably isn't exhaustive, but contributions are welcome. When in doubt, `use the source `_! See the `project page at GitHub `_! We also have `continuous integration at Travis-CI `_. The *nine* class decorator ========================== We provide a class decorator for Python 2 and 3 compatibility of magic methods. Magic methods are those that start and end with two underlines. You define the magic methods with their Python 3 names and, on Python 2, they get their corresponding names. You may write: * ``__next__()``. Use the ``next(iterator)`` function to iterate. * ``__str__()``: must return a unicode string. In Python 2, we implement ``__unicode__()`` and ``__bytes__()`` for you, based on your ``__str__()``. * ``__repr__()``: must return a unicode string. * ``__bytes__()``: must return a bytes object. Example:: @nine class MyClass(object): def __str__(self): return "MyClass" # a unicode string Porting steps ============= When you are starting to apply *nine* on Python 2 code to achieve Python 3 compatibility, you can start by following this list of tasks. It isn't exhaustive, just a good start. You can upgrade one ``.py`` module at a time: * Add our header as mentioned above. * Replace ocurrences of the print statement with the print function (this roughly means, add parentheses). * Replace ``str()``, usually with nine's ``native_str()`` or with ``bytes()``. * Replace ``unicode()`` with ``str()`` and ``from nine import str`` * Replace ``__unicode__()`` methods with ``__str__()`` methods; apply the ``@nine`` decorator on the class. * Also apply the ``@nine`` decorator on classes that define ``__repr__()``. * Search for ``range`` and replace with nine's ``range`` or ``range_list`` * Some dict methods return different things in Python 3. Only if you need exactly the same behavior in both versions, replace: * ``d.keys()`` or ``d.iterkeys()`` with nine's ``iterkeys(d)``; * ``d.values()`` or ``d.itervalues()`` with nine's ``itervalues(d)``; and * ``d.items()`` or ``d.iteritems()`` with nine's ``iteritems(d)``. * Notice that ``map()``, ``zip()`` and ``filter()``, in nine's versions, always return iterators independently of Python version. If you had been using *six* or another compatibility library before: * Replace ``string_types`` with nine's ``basestring`` Then run your tests in all the Python versions you wish to support. If I forgot to mention anything, could you `make a pull request `_, for the benefit of other developers? Keywords: python 2,python 3,python2,python3,migration,compatibility,nine,six,2to3,3to2,future Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: Public Domain Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 nine-1.1.0/README.rst0000644000175000017510000001647613611651304013522 0ustar nannan00000000000000Let's write Python 3 right now! =============================== When the best Python 2/Python 3 compatibility modules -- especially the famous `*six* library invented by Benjamin Peterson `_ -- were created, they were written from the point of view of a Python 2 programmer starting to grok Python 3. If you use *six*, your code is compatible, but stuck in Python 2 idioms. **nine** turns **six** upside down. You write your code using Python 3 idioms -- as much as possible --, and it is the Python 2 "version" that is patched. Needless to say, this approach is more future-proof. When thou writeth Python, thou shalt write Python 3 and, just for a little longer, ensure that the thing worketh on Python 2.7. *nine* facilitates this point of view. You can write code that is as 3ish as possible while still supporting 2.6. For instance, you don't type ``unicode`` anymore, you type ``str``, and *nine* makes ``str`` point to ``unicode`` on Python 2 (if you use our boilerplate). Also, ``map``, ``zip`` and ``filter`` have Python 3 behaviour, on Python 2, meaning they return iterators, not lists. Honestly you should not spend one thought on Python 2.6 anymore, it is `no longer supported `_ since its final release (2.6.9) in October 2013. Nobody uses 3.0 or 3.1 either. Python 2.7 has finally met its demise on the first day of 2020. *nine* is extremely stable and unlikely to change since it solves an old problem that never changes. Nobody should be surprised if *nine* isn't updated for months or even years. The author(s) of *nine* donate this module to the public domain. To understand most of the intricacies involved in achieving 2&3 compatibility in a single codebase, I recommend reading this: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ Using nine ========== In each of your modules, start by declaring a text encoding and importing Python 3 behaviours from __future__. Then import variables from *nine*, as per this boilerplate:: # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) from nine import (IS_PYTHON2, str, basestring, native_str, chr, long, integer_types, class_types, range, range_list, reraise, iterkeys, itervalues, iteritems, map, zip, filter, input, implements_iterator, implements_to_string, implements_repr, nine, nimport) I know that is ugly. What did you expect? *nine* is 3 squared. OK, in many cases you can get away with less:: # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) from nine import IS_PYTHON2, nimport, nine, range, str, basestring But in the second case you need to remember to import the missing stuff when you use it, and it is not realistic to expect that you will remember, is it? Unicode ======= Because of the ``unicode_literals`` import, **all string literals in the module become unicode objects**. No need to add a "u" prefix to each string literal. This is the saner approach since in Python 3 strings are unicode objects by default, and you can then indicate ``b"this is a byte string literal"``. The literals that actually need to be byte strings are very rare. But you wouldn't believe how many developers are irrationally afraid of taking this simple step... If you don't know much about Unicode, just read `The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) `_ Importing moved stuff ===================== Many standard library modules were renamed in Python 3, but nine can help. The ``nimport`` function gets the new Python 3 name, but knows to import the old name if running in Python 2. For instance, instead of writing this to import pickle:: # Bad: try: import cPickle as pickle # Python 2.x except ImportError: import pickle # Python 3 automatically uses the C version. ...you can write this:: # Good: pickle = nimport('pickle') For variables that have been moved: In the argument, please separate the module from the variable with a colon:: name2codepoint = nimport('html.entities:name2codepoint') Want StringIO? I recommend you build lists instead. But if you really need it:: # Good: if IS_PYTHON2: from cStringIO import StringIO as BytesIO, StringIO NativeStringIO = BytesIO else: from io import BytesIO, StringIO NativeStringIO = StringIO Our coverage of Python version differences probably isn't exhaustive, but contributions are welcome. When in doubt, `use the source `_! See the `project page at GitHub `_! We also have `continuous integration at Travis-CI `_. The *nine* class decorator ========================== We provide a class decorator for Python 2 and 3 compatibility of magic methods. Magic methods are those that start and end with two underlines. You define the magic methods with their Python 3 names and, on Python 2, they get their corresponding names. You may write: * ``__next__()``. Use the ``next(iterator)`` function to iterate. * ``__str__()``: must return a unicode string. In Python 2, we implement ``__unicode__()`` and ``__bytes__()`` for you, based on your ``__str__()``. * ``__repr__()``: must return a unicode string. * ``__bytes__()``: must return a bytes object. Example:: @nine class MyClass(object): def __str__(self): return "MyClass" # a unicode string Porting steps ============= When you are starting to apply *nine* on Python 2 code to achieve Python 3 compatibility, you can start by following this list of tasks. It isn't exhaustive, just a good start. You can upgrade one ``.py`` module at a time: * Add our header as mentioned above. * Replace ocurrences of the print statement with the print function (this roughly means, add parentheses). * Replace ``str()``, usually with nine's ``native_str()`` or with ``bytes()``. * Replace ``unicode()`` with ``str()`` and ``from nine import str`` * Replace ``__unicode__()`` methods with ``__str__()`` methods; apply the ``@nine`` decorator on the class. * Also apply the ``@nine`` decorator on classes that define ``__repr__()``. * Search for ``range`` and replace with nine's ``range`` or ``range_list`` * Some dict methods return different things in Python 3. Only if you need exactly the same behavior in both versions, replace: * ``d.keys()`` or ``d.iterkeys()`` with nine's ``iterkeys(d)``; * ``d.values()`` or ``d.itervalues()`` with nine's ``itervalues(d)``; and * ``d.items()`` or ``d.iteritems()`` with nine's ``iteritems(d)``. * Notice that ``map()``, ``zip()`` and ``filter()``, in nine's versions, always return iterators independently of Python version. If you had been using *six* or another compatibility library before: * Replace ``string_types`` with nine's ``basestring`` Then run your tests in all the Python versions you wish to support. If I forgot to mention anything, could you `make a pull request `_, for the benefit of other developers? nine-1.1.0/nine/0000755000175000017510000000000013611655256012760 5ustar nannan00000000000000nine-1.1.0/nine/__init__.py0000644000175000017510000002113113611646504015063 0ustar nannan00000000000000# -*- coding: utf-8 -*- """For documentation and usage, please see the file README.rst. This module is donated to the public domain. """ import sys # Test for Python 2, not 3; don't get bitten when Python 4 appears: IS_PYTHON2 = (sys.version_info[0] == 2) IS_PYPY = hasattr(sys, 'pypy_translation_info') from importlib import import_module if IS_PYTHON2: # Rename Python 2 builtins so they become like Python 3 native_str = bytes str = unicode basestring = basestring byte_chr = chr # does not seem to have an equivalent in Python 3. chr = unichr # takes an int and returns the corresponding unicode char integer_types = (int, long) long = long from types import ClassType class_types = (type, ClassType) del ClassType range_list = range range = xrange iterkeys = lambda d: d.iterkeys() itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() from itertools import ifilter as filter, imap as map, izip as zip # In Python 2, *input* was equivalent to eval(raw_input(prompt)): input = raw_input else: # For Python 3, declare these variables so they can be chain imported: basestring = native_str = str = str chr = chr # No need to do the same to ord() integer_types = (int,) long = int class_types = type range = range range_list = lambda *a: list(range(*a)) iterkeys = lambda d: iter(d.keys()) itervalues = lambda d: iter(d.values()) iteritems = lambda d: iter(d.items()) filter = filter map = map zip = zip input = input if IS_PYTHON2: # Turn code into string to avoid SyntaxError on Python 3: exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') else: def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value # ===== Class decorators ===== if IS_PYTHON2: def implements_to_string(cls): """Class decorator that converts __str__() and __bytes__. You define __str__() and it is moved to __unicode__() on Python 2. Additionally, if you define __bytes__(), it becomes __str__() on Python 2. If __bytes__() is not defined, __str__() executes __unicode__() and encodes the result to utf-8. """ cls.__unicode__ = cls.__str__ cls.__str__ = cls.__bytes__ if hasattr(cls, '__bytes__') \ else lambda x: x.__unicode__().encode('utf-8') return cls def implements_iterator(cls): """Class decorator. next() has been renamed to __next__().""" cls.next = cls.__next__ del cls.__next__ return cls def implements_repr(cls): """Class decorator that wraps __repr__() in Python 2. You implement __repr__() returning a unicode string, and in Python 2, I encode it to utf-8 for you. """ cls.__repr_unicode__ = cls.__repr__ def wrapper(self): return self.__repr_unicode__().encode('utf-8') cls.__repr__ = wrapper return cls def nine(cls): """Class decorator for Python 2 and 3 compatibility of magic methods. You define the magic methods with their Python 3 names and, on Python 2, they get their corresponding names. You may write: * __next__(). Use the next(iterator) function to iterate. * __str__(): must return a unicode string. * __repr__(): must return a unicode string. * __bytes__(): must return a bytes object. (*nine* is all the above class decorators in one.) """ if hasattr(cls, '__str__'): cls = implements_to_string(cls) if hasattr(cls, '__next__'): cls = implements_iterator(cls) if hasattr(cls, '__repr__'): cls = implements_repr(cls) return cls else: # On Python 3, these class decorators do nothing: implements_to_string = implements_iterator = implements_repr = nine = \ lambda cls: cls # http://docs.pythonsprints.com/python3_porting/py-porting.html _moved = { # Mapping from Python 3 to Python 2 location. May need improvement. 'builtins': '__builtin__', 'configparser': 'ConfigParser', 'copyreg': 'copy_reg', '_markupbase': 'markupbase', 'pickle': 'cPickle', 'queue': 'Queue', 'reprlib': 'repr', 'socketserver': 'SocketServer', '_thread': 'thread', 'tkinter': 'Tkinter', 'http.client': 'httplib', 'http.cookiejar': 'cookielib', 'http.cookies': 'Cookie', 'html.entities': 'htmlentitydefs', 'html.entities:entitydefs': 'htmlentitydefs:entitydefs', 'html.entities:name2codepoint': 'htmlentitydefs:name2codepoint', 'html.entities:codepoint2name': 'htmlentitydefs:codepoint2name', 'html:escape': 'cgi:escape', 'html.parser:HTMLParser': 'htmllib:HTMLParser', 'urllib.robotparser': 'robotparser', 'urllib.error:ContentTooShortError': 'urllib:ContentTooShortError', 'urllib.parse': 'urlparse', 'urllib.parse:quote': 'urllib:quote', 'urllib.parse:quote_plus': 'urllib:quote_plus', 'urllib.parse:unquote': 'urllib:unquote', 'urllib.parse:unquote_plus': 'urllib:unquote_plus', 'urllib.parse:urlencode': 'urllib:urlencode', 'urllib.request:getproxies': 'urllib:getproxies', 'urllib.request:pathname2url': 'urllib:pathname2url', 'urllib.request:url2pathname': 'urllib:url2pathname', 'urllib.request:urlcleanup': 'urllib:urlcleanup', 'urllib.request:urlretrieve': 'urllib:urlretrieve', 'urllib.request:URLopener': 'urllib:URLopener', 'urllib.request:FancyURLopener': 'urllib:FancyURLopener', 'urllib.request:urlopen': 'urllib2:urlopen', 'urllib.request:install_opener': 'urllib2:install_opener', 'urllib.request:build_opener': 'urllib2:build_opener', 'urllib.error:URLError': 'urllib2:URLError', 'urllib.error:HTTPError': 'urllib2:HTTPError', 'urllib.request:Request': 'urllib2:Request', 'urllib.request:OpenerDirector': 'urllib2:OpenerDirector', 'urllib.request:BaseHandler': 'urllib2:BaseHandler', 'urllib.request:HTTPDefaultErrorHandler': 'urllib2:HTTPDefaultErrorHandler', 'urllib.request:HTTPRedirectHandler': 'urllib2:HTTPRedirectHandler', 'urllib.request:HTTPCookieProcessor': 'urllib2:HTTPCookieProcessor', 'urllib.request:ProxyHandler': 'urllib2:ProxyHandler', 'urllib.request:HTTPPasswordMgr': 'urllib2:HTTPPasswordMgr', 'urllib.request:HTTPPasswordMgrWithDefaultRealm': 'urllib2:HTTPPasswordMgrWithDefaultRealm', 'urllib.request:AbstractBasicAuthHandler': 'urllib2:AbstractBasicAuthHandler', 'urllib.request:HTTPBasicAuthHandler': 'urllib2:HTTPBasicAuthHandler', 'urllib.request:ProxyBasicAuthHandler': 'urllib2:ProxyBasicAuthHandler', 'urllib.request:AbstractDigestAuthHandler': 'urllib2:AbstractDigestAuthHandler', 'urllib.request:HTTPDigestAuthHandler': 'urllib2:HTTPDigestAuthHandler', 'urllib.request:ProxyDigestAuthHandler': 'urllib2:ProxyDigestAuthHandler', 'urllib.request:HTTPHandler': 'urllib2:HTTPHandler', 'urllib.request:HTTPSHandler': 'urllib2:HTTPSHandler', 'urllib.request:FileHandler': 'urllib2:FileHandler', 'urllib.request:FTPHandler': 'urllib2:FTPHandler', 'urllib.request:CacheFTPHandler': 'urllib2:CacheFTPHandler', 'urllib.request:UnknownHandler': 'urllib2:UnknownHandler', } if sys.version_info < (3, 9): # dummy_thread has been removed from Python 3.9 # https://bugs.python.org/issue37312 _moved['_dummy_thread'] = 'dummy_thread' def nimport(spec): """Given a Python 3 resource spec, imports and returns it. Example usage:: join = nimport('os.path:join') The ":" indicates "join" is a variable in the module "os.path". The spec should provide the **new** location of the module or variable. *nine* is supposed to know the corresponding, old Python 2 location. Bug reports and pull requests are welcome. """ assert spec if IS_PYTHON2: # Get the Python 2 location of the name, first. spec = _moved.get(spec, spec) alist = spec.split(':') if len(alist) > 2: raise ValueError( 'The argument *spec* cannot have more than ' '2 colon-separated parts: "{}"'.format(spec)) elif len(alist) == 2: module, name = alist elif len(alist) == 1: module = alist[0] name = None module = import_module(module) return getattr(module, name) if name else module # don't export nine.six del sys nine-1.1.0/nine/decorator.py0000644000175000017510000000341313611646504015311 0ustar nannan00000000000000# -*- coding: utf-8 -*- '''*nine* is about Python 2 and 3 compatibility, but I have taken the liberty of including something else in it. The *reify* decorator is so useful that it should come with Python, in the standard library. While it doesn't, the next best thing is for it to be included in a "base" package. And *nine* is a base package! The *reify* decorator comes from Pyramid_ source code. This is fair use because it is only one function out of hundreds. .. _Pyramid: https://pypi.python.org/pypi/pyramid ''' from __future__ import (absolute_import, division, print_function, unicode_literals) class reify(object): """ Use as a class method decorator. It operates almost exactly like the Python ``@property`` decorator, but it puts the result of the method it decorates into the instance dict after the first call, effectively replacing the function it decorates with an instance variable. It is, in Python parlance, a non-data descriptor. An example: .. code-block:: python class Foo(object): @reify def jammy(self): print 'jammy called' return 1 And usage of Foo: .. code-block:: text >>> f = Foo() >>> v = f.jammy 'jammy called' >>> print v 1 >>> f.jammy 1 >>> # jammy func not called the second time; it replaced itself with 1 """ def __init__(self, wrapped): self.wrapped = wrapped try: self.__doc__ = wrapped.__doc__ except: # pragma: no cover pass def __get__(self, inst, objtype=None): if inst is None: return self val = self.wrapped(inst) setattr(inst, self.wrapped.__name__, val) return val nine-1.1.0/nine/test_nine.py0000644000175000017510000000135513611646504015322 0ustar nannan00000000000000# -*- coding: utf-8 -*- '''This module is donated to the public domain.''' from __future__ import (absolute_import, division, print_function, unicode_literals) import unittest class TestNine(unittest.TestCase): def test_import(self): from nine import ( IS_PYTHON2, str, basestring, native_str, integer_types, class_types, range, range_list, reraise, iterkeys, itervalues, iteritems, map, zip, filter, input, implements_iterator, implements_to_string, implements_repr, nine, nimport, _moved) for key in _moved: if key == 'tkinter': continue # travis does not have tk installed :p assert nimport(key) nine-1.1.0/nine.egg-info/0000755000175000017510000000000013611655256014452 5ustar nannan00000000000000nine-1.1.0/nine.egg-info/PKG-INFO0000644000175000017510000002343113611655256015552 0ustar nannan00000000000000Metadata-Version: 1.1 Name: nine Version: 1.1.0 Summary: Python 2 / 3 compatibility, like six, but favouring Python 3 Home-page: https://github.com/nandoflorestan/nine Author: Nando Florestan Author-email: nandoflorestan@gmail.com License: Public domain Description: Let's write Python 3 right now! =============================== When the best Python 2/Python 3 compatibility modules -- especially the famous `*six* library invented by Benjamin Peterson `_ -- were created, they were written from the point of view of a Python 2 programmer starting to grok Python 3. If you use *six*, your code is compatible, but stuck in Python 2 idioms. **nine** turns **six** upside down. You write your code using Python 3 idioms -- as much as possible --, and it is the Python 2 "version" that is patched. Needless to say, this approach is more future-proof. When thou writeth Python, thou shalt write Python 3 and, just for a little longer, ensure that the thing worketh on Python 2.7. *nine* facilitates this point of view. You can write code that is as 3ish as possible while still supporting 2.6. For instance, you don't type ``unicode`` anymore, you type ``str``, and *nine* makes ``str`` point to ``unicode`` on Python 2 (if you use our boilerplate). Also, ``map``, ``zip`` and ``filter`` have Python 3 behaviour, on Python 2, meaning they return iterators, not lists. Honestly you should not spend one thought on Python 2.6 anymore, it is `no longer supported `_ since its final release (2.6.9) in October 2013. Nobody uses 3.0 or 3.1 either. Python 2.7 has finally met its demise on the first day of 2020. *nine* is extremely stable and unlikely to change since it solves an old problem that never changes. Nobody should be surprised if *nine* isn't updated for months or even years. The author(s) of *nine* donate this module to the public domain. To understand most of the intricacies involved in achieving 2&3 compatibility in a single codebase, I recommend reading this: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ Using nine ========== In each of your modules, start by declaring a text encoding and importing Python 3 behaviours from __future__. Then import variables from *nine*, as per this boilerplate:: # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) from nine import (IS_PYTHON2, str, basestring, native_str, chr, long, integer_types, class_types, range, range_list, reraise, iterkeys, itervalues, iteritems, map, zip, filter, input, implements_iterator, implements_to_string, implements_repr, nine, nimport) I know that is ugly. What did you expect? *nine* is 3 squared. OK, in many cases you can get away with less:: # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) from nine import IS_PYTHON2, nimport, nine, range, str, basestring But in the second case you need to remember to import the missing stuff when you use it, and it is not realistic to expect that you will remember, is it? Unicode ======= Because of the ``unicode_literals`` import, **all string literals in the module become unicode objects**. No need to add a "u" prefix to each string literal. This is the saner approach since in Python 3 strings are unicode objects by default, and you can then indicate ``b"this is a byte string literal"``. The literals that actually need to be byte strings are very rare. But you wouldn't believe how many developers are irrationally afraid of taking this simple step... If you don't know much about Unicode, just read `The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) `_ Importing moved stuff ===================== Many standard library modules were renamed in Python 3, but nine can help. The ``nimport`` function gets the new Python 3 name, but knows to import the old name if running in Python 2. For instance, instead of writing this to import pickle:: # Bad: try: import cPickle as pickle # Python 2.x except ImportError: import pickle # Python 3 automatically uses the C version. ...you can write this:: # Good: pickle = nimport('pickle') For variables that have been moved: In the argument, please separate the module from the variable with a colon:: name2codepoint = nimport('html.entities:name2codepoint') Want StringIO? I recommend you build lists instead. But if you really need it:: # Good: if IS_PYTHON2: from cStringIO import StringIO as BytesIO, StringIO NativeStringIO = BytesIO else: from io import BytesIO, StringIO NativeStringIO = StringIO Our coverage of Python version differences probably isn't exhaustive, but contributions are welcome. When in doubt, `use the source `_! See the `project page at GitHub `_! We also have `continuous integration at Travis-CI `_. The *nine* class decorator ========================== We provide a class decorator for Python 2 and 3 compatibility of magic methods. Magic methods are those that start and end with two underlines. You define the magic methods with their Python 3 names and, on Python 2, they get their corresponding names. You may write: * ``__next__()``. Use the ``next(iterator)`` function to iterate. * ``__str__()``: must return a unicode string. In Python 2, we implement ``__unicode__()`` and ``__bytes__()`` for you, based on your ``__str__()``. * ``__repr__()``: must return a unicode string. * ``__bytes__()``: must return a bytes object. Example:: @nine class MyClass(object): def __str__(self): return "MyClass" # a unicode string Porting steps ============= When you are starting to apply *nine* on Python 2 code to achieve Python 3 compatibility, you can start by following this list of tasks. It isn't exhaustive, just a good start. You can upgrade one ``.py`` module at a time: * Add our header as mentioned above. * Replace ocurrences of the print statement with the print function (this roughly means, add parentheses). * Replace ``str()``, usually with nine's ``native_str()`` or with ``bytes()``. * Replace ``unicode()`` with ``str()`` and ``from nine import str`` * Replace ``__unicode__()`` methods with ``__str__()`` methods; apply the ``@nine`` decorator on the class. * Also apply the ``@nine`` decorator on classes that define ``__repr__()``. * Search for ``range`` and replace with nine's ``range`` or ``range_list`` * Some dict methods return different things in Python 3. Only if you need exactly the same behavior in both versions, replace: * ``d.keys()`` or ``d.iterkeys()`` with nine's ``iterkeys(d)``; * ``d.values()`` or ``d.itervalues()`` with nine's ``itervalues(d)``; and * ``d.items()`` or ``d.iteritems()`` with nine's ``iteritems(d)``. * Notice that ``map()``, ``zip()`` and ``filter()``, in nine's versions, always return iterators independently of Python version. If you had been using *six* or another compatibility library before: * Replace ``string_types`` with nine's ``basestring`` Then run your tests in all the Python versions you wish to support. If I forgot to mention anything, could you `make a pull request `_, for the benefit of other developers? Keywords: python 2,python 3,python2,python3,migration,compatibility,nine,six,2to3,3to2,future Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: Public Domain Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 nine-1.1.0/nine.egg-info/SOURCES.txt0000644000175000017510000000036513611655256016342 0ustar nannan00000000000000LICENSE.rst MANIFEST.in README.rst setup.cfg setup.py nine/__init__.py nine/decorator.py nine/test_nine.py nine.egg-info/PKG-INFO nine.egg-info/SOURCES.txt nine.egg-info/dependency_links.txt nine.egg-info/not-zip-safe nine.egg-info/top_level.txtnine-1.1.0/nine.egg-info/dependency_links.txt0000644000175000017510000000000113611655256020520 0ustar nannan00000000000000 nine-1.1.0/nine.egg-info/not-zip-safe0000644000175000017510000000000113611654154016674 0ustar nannan00000000000000 nine-1.1.0/nine.egg-info/top_level.txt0000644000175000017510000000000513611655256017177 0ustar nannan00000000000000nine nine-1.1.0/setup.cfg0000644000175000017510000000010313611655256013642 0ustar nannan00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 nine-1.1.0/setup.py0000755000175000017510000000336313611655256013551 0ustar nannan00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # http://peak.telecommunity.com/DevCenter/setuptools#developer-s-guide # from distutils.core import setup from setuptools import setup, find_packages from codecs import open with open('README.rst', encoding='utf-8') as f: long_description = f.read() requires = [] # Python 2.6 does not have importlib, but a package exists for that from sys import version_info if version_info[:2] < (2, 7): requires.append('importlib') setup( name="nine", version='1.1.0', description="Python 2 / 3 compatibility, like six, but favouring Python 3", long_description=long_description, url='https://github.com/nandoflorestan/nine', author='Nando Florestan', author_email="nandoflorestan@gmail.com", license='Public domain', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='nine', install_requires=requires, keywords=[ "python 2", 'python 3', 'python2', 'python3', 'migration', 'compatibility', 'nine', 'six', '2to3', '3to2', 'future', ], classifiers=[ # http://pypi.python.org/pypi?:action=list_classifiers "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", 'License :: Public Domain', "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", ], )