summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Peterson <benjamin@python.org>2010-11-20 15:52:32 -0600
committerBenjamin Peterson <benjamin@python.org>2010-11-20 15:52:32 -0600
commitdfc800b27e5b5b24519087e69766f20f1c16ade0 (patch)
tree8bd39447c99ff35a5a85e2e14aea916cd7b4980d
downloadsix-dfc800b27e5b5b24519087e69766f20f1c16ade0.tar.gz
import six
-rw-r--r--.hgignore5
-rw-r--r--LICENSE19
-rw-r--r--MANIFEST.in4
-rw-r--r--README12
-rw-r--r--documentation/Makefile130
-rw-r--r--documentation/conf.py217
-rw-r--r--documentation/index.rst346
-rw-r--r--setup.py27
-rw-r--r--six.py313
-rw-r--r--test_six.py285
10 files changed, 1358 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..cb1c3aa
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,5 @@
+syntax: glob
+*.pyc
+dist
+MANIFEST
+documentation/_build
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..653ab52
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+ Copyright (c) 2010 Benjamin Peterson
+
+ 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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..09d81fb
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include LICENSE
+
+recursive-include documentation *
+prune documentation/_build
diff --git a/README b/README
new file mode 100644
index 0000000..df1b868
--- /dev/null
+++ b/README
@@ -0,0 +1,12 @@
+Six is a Python 2 and 3 compatibility library. It provides utility functions
+for smoothing over the differences between the Python versions with the goal of
+writing Python code that is compatible on both Python versions. See the
+documentation for more information on what is provided.
+
+Six supports Python 2.4+.
+
+Online documentation is at http://packages.python.org/six/.
+
+Bugs can be reported to http://bugs.launchpad.net/python-six.
+
+Code is at http://code.launchpad.net/python-six.
diff --git a/documentation/Makefile b/documentation/Makefile
new file mode 100644
index 0000000..eebafcd
--- /dev/null
+++ b/documentation/Makefile
@@ -0,0 +1,130 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/six.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/six.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/six"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/six"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ make -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/documentation/conf.py b/documentation/conf.py
new file mode 100644
index 0000000..7872444
--- /dev/null
+++ b/documentation/conf.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+#
+# six documentation build configuration file
+
+import os
+import sys
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+needs_sphinx = "1.0"
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ["sphinx.ext.intersphinx"]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# The suffix of source filenames.
+source_suffix = ".rst"
+
+# The encoding of source files.
+#source_encoding = "utf-8-sig"
+
+# The master toctree document.
+master_doc = "index"
+
+# General information about the project.
+project = u"six"
+copyright = u"2010, Benjamin Peterson"
+
+sys.path.append(os.path.abspath(os.path.join(".", "..")))
+from six import __version__ as six_version
+sys.path.pop()
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = six_version[:-2]
+# The full version, including alpha/beta/rc tags.
+release = six_version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ["_build"]
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = "sphinx"
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = "default"
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'sixdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ("index", "six.tex", u"six Documentation",
+ u"Benjamin Peterson", "manual"),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ("index", "six", u"six Documentation",
+ [u"Benjamin Peterson"], 1)
+]
+
+# -- Intersphinx ---------------------------------------------------------------
+
+intersphinx_mapping = {"py2" : ("http://docs.python.org/", None),
+ "py3" : ("http://docs.python.org/py3k/", None)}
diff --git a/documentation/index.rst b/documentation/index.rst
new file mode 100644
index 0000000..c366337
--- /dev/null
+++ b/documentation/index.rst
@@ -0,0 +1,346 @@
+Six: Python 2 and 3 Compatibility Library
+=========================================
+
+.. module:: six
+ :synopsis: Python 2 and 3 compatibility
+
+.. moduleauthor:: Benjamin Peterson <benjamin@python.org>
+.. sectionauthor:: Benjamin Peterson <benjamin@python.org>
+
+
+Six provides simple utilities for wrapping over differences between Python 2 and
+Python 3.
+
+Six can be downloaded on `PyPi <http://pypi.python.org/pypi/six/>`_. Its bug
+tracker and code hosting is on `Launchpad <http://launchpad.net/python-six>`_.
+
+The name, "six", comes from the fact that 2*3 equals 6. Why not addition?
+Multiplication is more powerful, and, anyway, "five" has already been `snatched
+away <http://codespeak.net/z3/five/>`_.
+
+
+Indices and tables
+------------------
+
+* :ref:`genindex`
+* :ref:`search`
+
+
+Package contents
+----------------
+
+.. data:: PY3
+
+ A boolean indicating if the code is running on Python 3.
+
+
+Constants
+>>>>>>>>>
+
+Six provides constants that may differ between Python versions. Ones ending
+``_types`` are mostly useful as the second argument to ``isinstance`` or
+``issubclass``.
+
+
+.. data:: class_types
+
+ Possible class types. In Python 2, this encompasses old-style and new-style
+ classes. In Python 3, this is just new-styles.
+
+
+.. data:: integer_types
+
+ Possible integer types. In Python 2, this is :func:`py2:long` and
+ :func:`py2:int`, and in Python 3, just :func:`py3:int`.
+
+
+.. data:: string_types
+
+ Possible types for text data. This is :func:`py2:basestring` in Python 2 and
+ :func:`py3:str` in Python 3.
+
+
+.. data:: text_type
+
+ Type for representing textual data in Unicode. This is :func:`py2:unicode`
+ in Python 2 and :func:`py3:str` in Python 3.
+
+
+.. data:: binary_type
+
+ Type for representing binary data. This is :func:`py2:str` in Python 2 and
+ :func:`py3:bytes` in Python 3.
+
+
+.. data:: MAXSIZE
+
+ The maximum size of a container.
+
+
+Here's example usage of the module::
+
+ import six
+
+ def dispatch_types(value):
+ if isinstance(value, six.integer_types):
+ handle_integer(value)
+ elif isinstance(value, six.class_types):
+ handle_class(value)
+ elif isinstance(value, six.string_types):
+ handle_string(value)
+
+
+Object model compatibility
+>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+Python 3 renamed the attributes of several intepreter data structures. The
+following accessors are available. Note that the recommended way to inspect
+functions and methods is the stdlib :mod:`py3:inspect` module.
+
+
+.. function:: get_unbound_function(meth)
+
+ Get the function out of unbound method *meth*. In Python 3, unbound methods
+ don't exist, so this function just returns *meth* unchanged. Example
+ usage::
+
+ from six import get_unbound_function
+
+ class X(object):
+ def method(self):
+ pass
+ method_function = get_unbound_function(X.method)
+
+
+.. function:: get_method_function(meth)
+
+ Get the function out of method object *meth*.
+
+
+.. function:: get_method_self(meth)
+
+ Get the ``self`` of bound method *meth*.
+
+
+.. function:: get_function_code(func)
+
+ Get the code object associated with *func*.
+
+
+.. function:: get_function_defaults(func)
+
+ Get the defaults tuple associated with *func*.
+
+
+.. function:: advance_iterator(it)
+
+ Get the next item of iterator *it*. :exc:`py3:StopIteration` is raised if
+ the iterator is exhausted. This is a replacement for calling ``it.next()``
+ in Python 2 and ``next(it)`` in Python 3.
+
+
+.. function:: callable(obj)
+
+ Check if *obj* can be called.
+
+
+Syntax compatibility
+>>>>>>>>>>>>>>>>>>>>
+
+These functions smooth over operations which have different syntaxes between
+Python 2 and 3.
+
+
+.. function:: exec_(code, globals=None, locals=None)
+
+ Execute *code* in the scope of *globals* and *locals*. *code* can be a
+ string or a code object. If *globals* or *locals* is not given, they will
+ default to the scope of the caller. If just *globals* is given, it will also
+ be used as *locals*.
+
+
+.. function:: print_(*args, *, file=sys.stdout, end="\n", sep=" ")
+
+ Print *args* into *file*. Each argument will be separated with *sep* and
+ *end* will be written to the file at the last.
+
+ .. note::
+
+ In Python 2, this function imitates Python 3's :func:`py3:print` by not
+ having softspace support. If you don't know what that is, you're probably
+ ok. :)
+
+
+.. function:: reraise(exc_type, exc_value, exc_traceback=None)
+
+ Reraise an exception, possibly with a different traceback. In the simple
+ case, ``reraise(*sys.exc_info())`` with an active exception (in an except
+ block) reraises the current exception with the last traceback. A different
+ traceback can be specified with the *exc_traceback* parameter.
+
+
+.. function:: with_metaclass(metaclass, base=object)
+
+ Create a new class with base class *base* and metaclass *metaclass*. This is
+ designed to be used in class declarations like this: ::
+
+ from six import with_metaclass
+
+ class Meta(type):
+ pass
+
+ class Base(object):
+ pass
+
+ class MyClass(with_metaclass(Meta, Base)):
+ pass
+
+
+Binary and text data
+>>>>>>>>>>>>>>>>>>>>
+
+Python 3 enforces the distinction between far more rigoriously than does Python
+2; binary data cannot be automatically coerced text data. six provides the
+several functions to assist in classifying string data in all Python versions.
+
+
+.. function:: b(data)
+
+ A "fake" bytes literal. *data* should always be a normal string literal. In
+ Python 2, :func:`b` returns a 8-bit string. In Python 3, *data* is encoded
+ with the latin-1 encoding to bytes.
+
+
+.. function:: u(text)
+
+ A "fake" unicode literal. *text* should always be a normal string literal.
+ In Python 2, :func:`u` returns unicode, and in Python 3, a string.
+
+
+.. data:: StringIO
+
+ This is an fake file object for textual data. It's an alias for
+ :class:`py2:StringIO.StringIO` in Python 2 and :class:`py3:io.StringIO` in
+ Python 3.
+
+
+.. data:: BytesIO
+
+ This is a fake file object for binary data. In Python 2, it's an alias for
+ :class:`py2:StringIO.StringIO`, but in Python 3, it's an alias for
+ :class:`py3:io.BytesIO`.
+
+
+Renamed modules and attributes compatibility
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+.. module:: six.moves
+ :synopsis: Renamed modules and attributes compatibility
+
+Python 3 reorganized the standard library and moved several functions to
+different modules. This module provides a consistent interface to them. For
+example, to load the module for parsing HTML on Python 2 or 3, write::
+
+ from six.moves import html_parser
+
+Similarly, to get the function to reload modules, which was moved from the
+builtin module to the ``imp`` module, use::
+
+ from six.moves import reload_module
+
+For the most part, :mod:`six.moves` aliases are the names of the modules in
+Python 3. When the new Python 3 name is a package, the components of the name
+are separated by underscores. For example, ``html.parser`` becomes
+``html_parser``. In some cases where several modules have been combined, the
+Python 2 name is retained. This is so the appropiate modules can be found when
+running on Python 2. For example, ``BaseHTTPServer`` which is in
+``http.server`` in Python 3 is aliased as ``BaseHTTPServer``.
+
+Some modules which had two implementations have been merged in Python 3. For
+example, ``cPickle`` no longer exists in Python 3. It's been merged with
+``pickle``. In these cases, fetching the fast version will load the fast one on
+Python 2 and the merged module in Python 3.
+
+
+.. note::
+
+ The :mod:`py2:urllib`, :mod:`py2:urllib2`, and :mod:`py2:urlparse` modules
+ have been combined in the :mod:`py3:urllib` package in Python 3.
+ :mod:`six.moves` doesn't not support their renaming because their members
+ have been mixed across several modules in that package.
+
+Supported renames:
+
++------------------------------+-------------------------------------+---------------------------------+
+| Name | Python 2 name | Python 3 name |
++==============================+=====================================+=================================+
+| ``builtins`` | :mod:`py2:__builtin__` | :mod:`py3:builtins` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``configparser`` | :mod:`py2:ConfigParser` | :mod:`py3:configparser` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``copyreg`` | :mod:`py2:copy_reg` | :mod:`py3:copyreg` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``cPickle`` | :mod:`py2:cPickle` | :mod:`py3:pickle` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``cStringIO`` | :func:`py2:cStringIO.StringIO` | :class:`py3:io.StringIO` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``http_cookiejar`` | :mod:`py2:cookielib` | :mod:`py3:http.cookiejar` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``http_cookies`` | :mod:`py2:Cookie` | :mod:`py3:http.cookies` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``html_entities`` | :mod:`py2:htmlentitydefs` | :mod:`py3:html.entities` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``html_parser`` | :mod:`py2:HTMLParser` | :mod:`py3:html.parser` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``http_client`` | :mod:`py2:httplib` | :mod:`py3:http.client` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``BaseHTTPServer`` | :mod:`py2:BaseHTTPServer` | :mod:`py3:http.server` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``CGIHTTPServer`` | :mod:`py2:CGIHTTPServer` | :mod:`py3:http.server` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``SimpleHTTPServer`` | :mod:`py2:SimpleHTTPServer` | :mod:`py3:http.server` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``queue`` | :mod:`py2:Queue` | :mod:`py3:queue` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``reduce`` | :func:`py2:reduce` | :func:`py3:functools.reduce` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``reload_module`` | :func:`py2:reload` | :func:`py3:imp.reload` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``reprlib`` | :mod:`py2:repr` | :mod:`py3:reprlib` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``socketserver`` | :mod:`py2:SocketServer` | :mod:`py3:socketserver` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter`` | :mod:`py2:Tkinter` | :mod:`py3:tkinter` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_dialog`` | :mod:`py2:Dialog` | :mod:`py3:tkinter.dialog` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_filedialog`` | :mod:`py2:FileDialog` | :mod:`py3:tkinter.FileDialog` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_scrolledtext`` | :mod:`py2:ScrolledText` | :mod:`py3:tkinter.scolledtext` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_simpledialog`` | :mod:`py2:SimpleDialog` | :mod:`py2:tkinter.simpledialog` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_tix`` | :mod:`py2:Tix` | :mod:`py3:tkinter.tix` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_constants`` | :mod:`py2:Tkconstants` | :mod:`py3:tkinter.constants` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_dnd`` | :mod:`py2:Tkdnd` | :mod:`py3:tkinter.dnd` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_colorchooser`` | :mod:`py2:tkColorChooser` | :mod:`py3:tkinter.colorchooser` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_commondialog`` | :mod:`py2:tkCommonDialog` | :mod:`py3:tkinter.commondialog` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_tkfiledialog`` | :mod:`py2:tkFileDialog` | :mod:`py3:tkinter.filedialog` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_font`` | :mod:`py2:tkFont` | :mod:`py3:tkinter.font` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_messagebox`` | :mod:`py2:tkMessageBox` | :mod:`py3:tkinter.messagebox` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``tkinter_tksimpledialog`` | :mod:`py2:tkSimpleDialog` | :mod:`py3:tkinter.simpledialog` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``urllib_robotparser`` | :mod:`py2:robotparser` | :mod:`py3:urllib.robotparser` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``winreg`` | :mod:`py2:_winreg` | :mod:`py3:winreg` |
++------------------------------+-------------------------------------+---------------------------------+
+| ``xrange`` | :func:`py2:xrange` | :func:`py3:range` |
++------------------------------+-------------------------------------+---------------------------------+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..90aee09
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,27 @@
+from distutils.core import setup
+
+import six
+
+six_classifiers = [
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 3",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Topic :: Software Development :: Libraries",
+ "Topic :: Utilities",
+]
+
+with open("README", "r") as fp:
+ six_long_description = fp.read()
+
+
+setup(name="six",
+ version=six.__version__,
+ author="Benjamin Peterson",
+ author_email="benjamin@python.org",
+ url="http://pypi.python.org/pypi/six/",
+ py_modules=["six"],
+ description="Python 2 and 3 compatibility utilities",
+ long_description=six_long_description,
+ classifiers=six_classifiers
+ )
diff --git a/six.py b/six.py
new file mode 100644
index 0000000..f55914b
--- /dev/null
+++ b/six.py
@@ -0,0 +1,313 @@
+"""Utilities for writing code that runs on Python 2 and 3"""
+
+import sys
+import types
+
+
+__version__ = "1.0b1"
+
+
+# True if we are running on Python 3.
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+ string_types = str,
+ integer_types = int,
+ class_types = type,
+ text_type = str
+ binary_type = bytes
+
+ MAXSIZE = sys.maxsize
+else:
+ string_types = basestring,
+ integer_types = (int, long)
+ class_types = (type, types.ClassType)
+ text_type = unicode
+ binary_type = str
+
+ MAXSIZE = sys.maxint
+
+
+def _add_doc(func, doc):
+ """Add documentation to a function."""
+ func.__doc__ = doc
+
+
+def _import_module(name):
+ """Import module, returning last module in string."""
+ __import__(name)
+ return sys.modules[name]
+
+
+class _LazyDescr(object):
+
+ def __init__(self, name):
+ self.name = name
+
+ def __get__(self, obj, tp):
+ result = self._resolve()
+ setattr(obj, self.name, result)
+ # This is a bit ugly, but it avoids running this again.
+ delattr(tp, self.name)
+ return result
+
+
+class _Module(_LazyDescr):
+
+ def __init__(self, name, old, new=None):
+ super(_Module, self).__init__(name)
+ if PY3:
+ if new is None:
+ new = name
+ self.mod = new
+ else:
+ self.mod = old
+
+ def _resolve(self):
+ return _import_module(self.mod)
+
+
+class _Attribute(_LazyDescr):
+
+ def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+ super(_Attribute, self).__init__(name)
+ if PY3:
+ if new_mod is None:
+ new_mod = name
+ self.mod = new_mod
+ if new_attr is None:
+ if old_attr is None:
+ new_attr = name
+ else:
+ new_attr = old_attr
+ self.attr = new_attr
+ else:
+ self.mod = old_mod
+ if old_attr is None:
+ old_attr = name
+ self.attr = old_attr
+
+ def _resolve(self):
+ module = _import_module(self.mod)
+ return getattr(module, self.attr)
+
+
+
+class _MovedItems(types.ModuleType):
+ """Lazy loading of moved objects"""
+
+
+_moved_attributes = [
+ _Attribute("cStringIO", "cStringIO", "io", "StringIO"),
+ _Attribute("reload_module", "__builtin__", "imp", "reload"),
+ _Attribute("reduce", "__builtin__", "functools"),
+ _Attribute("StringIO", "StringIO", "io"),
+ _Attribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+
+ _Module("builtins", "__builtin__"),
+ _Module("configparser", "ConfigParser"),
+ _Module("copyreg", "copy_reg"),
+ _Module("http_cookiejar", "cookielib", "http.cookiejar"),
+ _Module("http_cookies", "Cookie", "http.cookies"),
+ _Module("html_entities", "htmlentitydefs", "html.entities"),
+ _Module("html_parser", "HTMLParser", "html.parser"),
+ _Module("http_client", "httplib", "http.client"),
+ _Module("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+ _Module("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+ _Module("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+ _Module("cPickle", "cPickle", "pickle"),
+ _Module("queue", "Queue"),
+ _Module("reprlib", "repr"),
+ _Module("socketserver", "SocketServer"),
+ _Module("tkinter", "Tkinter"),
+ _Module("tkinter_dialog", "Dialog", "tkinter.dialog"),
+ _Module("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+ _Module("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+ _Module("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+ _Module("tkinter_tix", "Tix", "tkinter.tix"),
+ _Module("tkinter_constants", "Tkconstants", "tkinter.constants"),
+ _Module("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+ _Module("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"),
+ _Module("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"),
+ _Module("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+ _Module("tkinter_font", "tkFont", "tkinter.font"),
+ _Module("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+ _Module("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"),
+ _Module("urllib_robotparser", "robotparser", "urllib.robotparser"),
+ _Module("winreg", "_winreg"),
+]
+for attr in _moved_attributes:
+ setattr(_MovedItems, attr.name, attr)
+del attr
+
+moves = sys.modules["six.moves"] = _MovedItems("moves")
+
+
+if PY3:
+ _meth_func = "__func__"
+ _meth_self = "__self__"
+
+ _func_code = "__code__"
+ _func_defaults = "__defaults__"
+else:
+ _meth_func = "im_func"
+ _meth_self = "im_self"
+
+ _func_code = "func_code"
+ _func_defaults = "func_defaults"
+
+
+if PY3:
+ def get_unbound_function(unbound):
+ return unbound
+
+
+ advance_iterator = next
+
+ def callable(obj):
+ return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+else:
+ def get_unbound_function(unbound):
+ return unbound.im_func
+
+
+ def advance_iterator(it):
+ return it.next()
+
+ callable = callable
+_add_doc(get_unbound_function,
+ """Get the function out of a possibly unbound function""")
+
+
+def get_method_function(meth):
+ """Get the underlying function of a bound method."""
+ return getattr(meth, _meth_func)
+
+
+def get_method_self(meth):
+ """Get the self of a bound method."""
+ return getattr(meth, _meth_self)
+
+
+def get_function_code(func):
+ """Get code object of a function."""
+ return getattr(func, _func_code)
+
+
+def get_function_defaults(func):
+ """Get defaults of a function."""
+ return getattr(func, _func_defaults)
+
+
+if PY3:
+ def b(s):
+ return s.encode("latin-1")
+ def u(s):
+ return s
+ import io
+ StringIO = io.StringIO
+ BytesIO = io.BytesIO
+else:
+ def b(s):
+ return s
+ def u(s):
+ return unicode(s)
+ import StringIO
+ StringIO = BytesIO = StringIO.StringIO
+_add_doc(b, """Byte literal""")
+_add_doc(u, """Text literal""")
+
+
+if PY3:
+ exec_ = eval("exec")
+
+
+ def reraise(tp, value, tb=None):
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+
+
+ print_ = eval("print")
+
+
+ def with_metaclass(meta, base=object):
+ ns = dict(base=base, meta=meta)
+ exec_("""class NewBase(base, metaclass=meta):
+ pass""", ns)
+ return ns["NewBase"]
+
+
+else:
+ def exec_(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""")
+
+
+ exec_("""def reraise(tp, value, tb=None):
+ raise tp, value, tb
+""")
+
+
+ def print_(*args, **kwargs):
+ """The new-style print function."""
+ fp = kwargs.pop("file", sys.stdout)
+ if fp is None:
+ return
+ def write(data):
+ if not isinstance(data, basestring):
+ data = str(data)
+ fp.write(data)
+ want_unicode = False
+ sep = kwargs.pop("sep", None)
+ if sep is not None:
+ if isinstance(sep, unicode):
+ want_unicode = True
+ elif not isinstance(sep, str):
+ raise TypeError("sep must be None or a string")
+ end = kwargs.pop("end", None)
+ if end is not None:
+ if isinstance(end, unicode):
+ want_unicode = True
+ elif not isinstance(end, str):
+ raise TypeError("end must be None or a string")
+ if kwargs:
+ raise TypeError("invalid keyword arguments to print()")
+ if not want_unicode:
+ for arg in args:
+ if isinstance(arg, unicode):
+ want_unicode = True
+ break
+ if want_unicode:
+ newline = unicode("\n")
+ space = unicode(" ")
+ else:
+ newline = "\n"
+ space = " "
+ if sep is None:
+ sep = space
+ if end is None:
+ end = newline
+ for i, arg in enumerate(args):
+ if i:
+ write(sep)
+ write(arg)
+ write(end)
+
+
+ def with_metaclass(meta, base=object):
+ class NewBase(base):
+ __metaclass__ = meta
+ return NewBase
+
+
+_add_doc(reraise, """Reraise an exception.""")
+_add_doc(with_metaclass, """Create a base class with a metaclass""")
diff --git a/test_six.py b/test_six.py
new file mode 100644
index 0000000..651ed02
--- /dev/null
+++ b/test_six.py
@@ -0,0 +1,285 @@
+import operator
+import sys
+import types
+
+import py
+
+import six
+
+
+def test_add_doc():
+ def f():
+ """Icky doc"""
+ pass
+ six._add_doc(f, """New doc""")
+ assert f.__doc__ == "New doc"
+
+
+def test_import_module():
+ from email import errors
+ m = six._import_module("email.errors")
+ assert m is errors
+
+
+def test_integer_types():
+ assert isinstance(1, six.integer_types)
+ assert isinstance(-1, six.integer_types)
+ assert isinstance(six.MAXSIZE + 23, six.integer_types)
+ assert not isinstance(.1, six.integer_types)
+
+
+def test_string_types():
+ assert isinstance("hi", six.string_types)
+ assert isinstance(six.u("hi"), six.string_types)
+ assert issubclass(six.text_type, six.string_types)
+
+
+def test_class_types():
+ class X:
+ pass
+ class Y(object):
+ pass
+ assert isinstance(X, six.class_types)
+ assert isinstance(Y, six.class_types)
+ assert not isinstance(X(), six.class_types)
+
+
+def test_text_type():
+ assert type(six.u("hi")) is six.text_type
+
+
+def test_binary_type():
+ assert type(six.b("hi")) is six.binary_type
+
+
+def test_MAXSIZE():
+ py.test.raises(OverflowError, operator.mul, [None], six.MAXSIZE + 1)
+
+
+def test_lazy():
+ if six.PY3:
+ html_name = "html.parser"
+ else:
+ html_name = "HTMLParser"
+ assert html_name not in sys.modules
+ mod = six.moves.html_parser
+ assert sys.modules[html_name] is mod
+ assert "htmlparser" not in six._MovedItems.__dict__
+
+
+def pytest_generate_tests(metafunc):
+ if "item_name" in metafunc.funcargnames:
+ for value in six._moved_attributes:
+ if value.name == "winreg" and not sys.platform.startswith("win"):
+ continue
+ metafunc.addcall({"item_name" : value.name})
+
+
+def test_move_items(item_name):
+ """Ensure that everything loads correctly."""
+ getattr(six.moves, item_name)
+
+
+def test_get_unbound_function():
+ class X(object):
+ def m(self):
+ pass
+ assert six.get_unbound_function(X.m) is X.__dict__["m"]
+
+
+def test_get_method_self():
+ class X(object):
+ def m(self):
+ pass
+ x = X()
+ assert six.get_method_self(x.m) is x
+ py.test.raises(AttributeError, six.get_method_self, 42)
+
+
+def test_get_method_function():
+ class X(object):
+ def m(self):
+ pass
+ x = X()
+ assert six.get_method_function(x.m) is X.__dict__["m"]
+ py.test.raises(AttributeError, six.get_method_function, hasattr)
+
+
+def test_get_function_code():
+ def f():
+ pass
+ assert isinstance(six.get_function_code(f), types.CodeType)
+ py.test.raises(AttributeError, six.get_function_code, hasattr)
+
+
+def test_get_function_defaults():
+ def f(x, y=3, b=4):
+ pass
+ assert six.get_function_defaults(f) == (3, 4)
+
+
+def test_advance_iterator():
+ l = [1, 2]
+ it = iter(l)
+ assert six.advance_iterator(it) == 1
+ assert six.advance_iterator(it) == 2
+ py.test.raises(StopIteration, six.advance_iterator, it)
+ py.test.raises(StopIteration, six.advance_iterator, it)
+
+
+def test_callable():
+ class X:
+ def __call__(self):
+ pass
+ def method(self):
+ pass
+ assert six.callable(X)
+ assert six.callable(X())
+ assert six.callable(test_callable)
+ assert six.callable(hasattr)
+ assert six.callable(X.method)
+ assert six.callable(X().method)
+ assert not six.callable(4)
+ assert not six.callable("string")
+
+
+if six.PY3:
+
+ def test_b():
+ data = six.b("\xff")
+ assert isinstance(data, bytes)
+ assert len(data) == 1
+ assert data == bytes([255])
+
+
+ def test_u():
+ s = six.u("hi")
+ assert isinstance(s, str)
+ assert s == "hi"
+
+else:
+
+ def test_b():
+ data = six.b("\xff")
+ assert isinstance(data, str)
+ assert len(data) == 1
+ assert data == "\xff"
+
+
+ def test_u():
+ s = six.u("hi")
+ assert isinstance(s, unicode)
+ assert s == "hi"
+
+
+def test_StringIO():
+ fp = six.StringIO()
+ fp.write(six.u("hello"))
+ assert fp.getvalue() == six.u("hello")
+
+
+def test_BytesIO():
+ fp = six.BytesIO()
+ fp.write(six.b("hello"))
+ assert fp.getvalue() == six.b("hello")
+
+
+def test_exec_():
+ def f():
+ l = []
+ six.exec_("l.append(1)")
+ assert l == [1]
+ f()
+ ns = {}
+ six.exec_("x = 42", ns)
+ assert ns["x"] == 42
+ glob = {}
+ loc = {}
+ six.exec_("global y; y = 42; x = 12", glob, loc)
+ assert glob["y"] == 42
+ assert "x" not in glob
+ assert loc["x"] == 12
+ assert "y" not in loc
+
+
+def test_reraise():
+ def get_next(tb):
+ if six.PY3:
+ return tb.tb_next.tb_next
+ else:
+ return tb.tb_next
+ e = Exception("blah")
+ try:
+ raise e
+ except Exception:
+ tp, val, tb = sys.exc_info()
+ try:
+ six.reraise(tp, val, tb)
+ except Exception:
+ tp2, value2, tb2 = sys.exc_info()
+ assert tp2 is Exception
+ assert value2 is e
+ assert tb is get_next(tb2)
+ try:
+ six.reraise(tp, val)
+ except Exception:
+ tp2, value2, tb2 = sys.exc_info()
+ assert tp2 is Exception
+ assert value2 is e
+ assert tb2 is not tb
+ try:
+ six.reraise(tp, val, tb2)
+ except Exception:
+ tp2, value2, tb3 = sys.exc_info()
+ assert tp2 is Exception
+ assert value2 is e
+ assert get_next(tb3) is tb2
+
+
+def test_print_():
+ save = sys.stdout
+ out = sys.stdout = six.moves.StringIO()
+ try:
+ six.print_("Hello,", "person!")
+ finally:
+ sys.stdout = save
+ assert out.getvalue() == "Hello, person!\n"
+ out = six.StringIO()
+ six.print_("Hello,", "person!", file=out)
+ assert out.getvalue() == "Hello, person!\n"
+ out = six.StringIO()
+ six.print_("Hello,", "person!", file=out, end="")
+ assert out.getvalue() == "Hello, person!"
+ out = six.StringIO()
+ six.print_("Hello,", "person!", file=out, sep="X")
+ assert out.getvalue() == "Hello,Xperson!\n"
+ out = six.StringIO()
+ six.print_(six.u("Hello,"), six.u("person!"), file=out)
+ result = out.getvalue()
+ assert isinstance(result, six.text_type)
+ assert result == six.u("Hello, person!\n")
+ six.print_("Hello", file=None) # This works.
+ out = six.StringIO()
+ six.print_(None, file=out)
+ assert out.getvalue() == "None\n"
+
+
+def test_print_exceptions():
+ py.test.raises(TypeError, six.print_, x=3)
+ py.test.raises(TypeError, six.print_, end=3)
+ py.test.raises(TypeError, six.print_, sep=42)
+
+
+def test_with_metaclass():
+ class Meta(type):
+ pass
+ class X(six.with_metaclass(Meta)):
+ pass
+ assert type(X) is Meta
+ assert issubclass(X, object)
+ class Base(object):
+ pass
+ class X(six.with_metaclass(Meta, Base)):
+ pass
+ assert type(X) is Meta
+ assert issubclass(X, Base)