diff --git a/.hgignore b/.hgignore new file mode 100644 index 000000000..cb1c3aa71 --- /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 000000000..653ab52ac --- /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 000000000..09d81fb32 --- /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 000000000..df1b8689a --- /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 000000000..eebafcd6d --- /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 ' where 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 000000000..787244473 --- /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 +# " v 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 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 000000000..c36633718 --- /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 +.. sectionauthor:: Benjamin Peterson + + +Six provides simple utilities for wrapping over differences between Python 2 and +Python 3. + +Six can be downloaded on `PyPi `_. Its bug +tracker and code hosting is on `Launchpad `_. + +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 `_. + + +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 000000000..90aee0907 --- /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 000000000..f55914b6b --- /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 000000000..651ed0240 --- /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)