Merge tag '0.4' into master
am: 4edfc03559

Change-Id: I5f0a32ca9786c0d65de5f779f4c3f95637400d0f
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..d83bfc2
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,6 @@
+[run]
+source=funcsigs
+omit=funcsigs/odict*
+
+[report]
+include=funcsigs*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d2a7ab3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,17 @@
+language: python
+python:
+  - 2.6
+  - 2.7
+  - 3.2
+  - 3.3
+  - pypy
+install:
+  - pip install -r requirements/development.txt -r requirements/production.txt
+  - python setup.py install
+script:
+  - coverage run setup.py test
+  - coverage report --show-missing
+after_success:
+  - coveralls
+notifications:
+    email: aaron.iles+travis-ci@gmail.com
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..602eec5
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,19 @@
+Changelog
+---------
+
+0.4 (2013-12-20)
+````````````````
+* Fix unbound methods getting their first parameter curried
+* Publish Python wheel packages
+
+0.3 (2013-05-29)
+````````````````
+* Fix annotation formatting of builtin types on Python 2.x
+
+0.2 (2012-01-07)
+````````````````
+* PyPy compatability
+
+0.1 (2012-01-06)
+````````````````
+* Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3e563d6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2013 Aaron Iles
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..f0abb42
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,7 @@
+recursive-include docs *
+recursive-include tests *.py
+include *.py
+include CHANGELOG
+include LICENSE
+include MANIFEST.in
+include README.rst
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e232923
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+SHELL := /bin/bash
+
+deps:
+	pip install --upgrade \
+	            -r requirements/development.txt \
+	            -r requirements/production.txt
+
+sdist:
+	python setup.py sdist
+	python setup.py bdist_wheel
+
+register:
+	python setup.py register
+	python setup.py sdist upload
+	python setup.py bdist_wheel upload
+
+site:
+	cd docs; make html
+
+test:
+	coverage run setup.py test
+
+unittest:
+	coverage run -m unittest discover
+
+lint:
+	flake8 --exit-zero funcsigs tests
+
+coverage:
+	coverage report --show-missing
+
+clean:
+	python setup.py clean --all
+	find . -type f -name "*.pyc" -exec rm '{}' +
+	find . -type d -name "__pycache__" -exec rmdir '{}' +
+	rm -rf *.egg-info .coverage
+	cd docs; make clean
+
+docs: site
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..f04b7b4
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,83 @@
+funcsigs
+========
+
+``funcsigs`` is a backport of the `PEP 362`_ function signature features from
+Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7
+as well as 3.2 and up.
+
+|pypi_version|
+
+Documentation
+-------------
+
+The reference documentation is standard library documentation for the
+`inspect`_ module in Python3. This documentation has been included in the
+``funcsigs`` package documentation hosted on `Read The Docs`_.
+
+Example
+-------
+
+To obtain a signature object, pass the target function to the
+``funcsigs.signature`` function. ::
+
+    >>> from funcsigs import signature
+    >>> def foo(a, b=None, *args, **kwargs):
+    ...     pass
+
+    >>> sig = signature(foo)
+
+For the details of the signature object, refer to the either the package of
+standard library documentation.
+
+Compatability
+-------------
+
+The ``funcsigs`` backport has been tested against:
+
+* CPython 2.6
+* CPython 2.7
+* CPython 3.2
+* PyPy 1.9
+
+Continuous integration testing is provided by `Travis CI`_.
+
+Under Python 2.x there is a compatability issue when a function is assigned to
+the ``__wrapped__`` property of a class after it has been constructed.
+Similiarily there under PyPy directly passing the ``__call__`` method of a
+builtin is also a compatability issues.  Otherwise the functionality is
+believed to be uniform between both Python2 and Python3.
+
+Issues
+------
+
+Source code for ``funcsigs`` is hosted on `GitHub`_. Any bug reports or feature
+requests can be made using GitHub's `issues system`_. |build_status| |coverage|
+
+Copyright
+---------
+
+This is a derived work of CPython under the terms of the `PSF License
+Agreement`_. The original CPython inspect module, its unit tests and
+documentation are the copyright of the Python Software Foundation. The derived
+work is distributed under the `Apache License Version 2.0`_.
+
+.. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0
+.. _GitHub: https://github.com/aliles/funcsigs
+.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
+.. _Travis CI: http://travis-ci.org/
+.. _Read The Docs: http://funcsigs.readthedocs.org/
+.. _PEP 362: http://www.python.org/dev/peps/pep-0362/
+.. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object
+.. _issues system: https://github.com/alies/funcsigs/issues
+
+.. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master
+   :target: http://travis-ci.org/#!/aliles/funcsigs
+   :alt: Current build status
+
+.. |coverage| image:: https://coveralls.io/repos/aliles/funcsigs/badge.png?branch=master
+   :target: https://coveralls.io/r/aliles/funcsigs?branch=master
+   :alt: Coverage status
+
+.. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png
+   :target: https://crate.io/packages/funcsigs/
+   :alt: Latest PyPI version
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..f7ab3d1
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,153 @@
+# 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) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+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 "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@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/funcsigs.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/funcsigs.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/funcsigs"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/funcsigs"
+	@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."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+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/docs/_templates/page.html b/docs/_templates/page.html
new file mode 100644
index 0000000..5e1e00b
--- /dev/null
+++ b/docs/_templates/page.html
@@ -0,0 +1,9 @@
+{% extends "!page.html" %}
+{% block extrahead %}
+    <a href="https://github.com/aliles/funcsigs">
+        <img style="position: absolute; top: 0; right: 0; border: 0;"
+             src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"
+             alt="Fork me on GitHub">
+    </a>
+    {{ super() }}
+{% endblock %}
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..c6e4194
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# funcsigs documentation build configuration file, created by
+# sphinx-quickstart on Fri Apr 20 20:27:52 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# 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.insert(0, 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.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
+
+# 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 = 'funcsigs'
+copyright = '2013, Aaron Iles'
+
+# 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.
+from funcsigs import __version__
+version = '.'.join(__version__.split('.')[:2])
+# The full version, including alpha/beta/rc tags.
+release = __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 = 'agogo'
+
+# 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 = []
+
+# 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 = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'funcsigsdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'funcsigs.tex', 'funcsigs Documentation',
+   'Aaron Iles', '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
+
+# 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', 'funcsigs', 'funcsigs Documentation',
+     ['Aaron Iles'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'funcsigs', 'funcsigs Documentation',
+   'Aaron Iles', 'funcsigs', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {
+        'python3': ('http://docs.python.org/py3k', None),
+        'python': ('http://docs.python.org/', None)
+}
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..5d0f42f
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,315 @@
+.. funcsigs documentation master file, created by
+   sphinx-quickstart on Fri Apr 20 20:27:52 2012.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Introducing funcsigs
+====================
+
+The Funcsigs Package
+--------------------
+
+*funcsigs* is a backport of the `PEP 362`_ function signature features from
+Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7
+as well as 3.2 and up.
+
+.. _PEP 362: http://www.python.org/dev/peps/pep-0362/
+.. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object
+
+Compatability
+`````````````
+
+The *funcsigs* backport has been tested against:
+
+* CPython 2.6
+* CPython 2.7
+* CPython 3.2
+* PyPy 1.9
+
+Continuous integration testing is provided by `Travis CI`_.
+
+Under Python 2.x there is a compatability issue when a function is assigned to
+the ``__wrapped__`` property of a class after it has been constructed.
+Similiarily there under PyPy directly passing the ``__call__`` method of a
+builtin is also a compatability issues.  Otherwise the functionality is
+believed to be uniform between both Python2 and Python3.
+
+.. _Travis CI: http://travis-ci.org/
+
+Issues
+``````
+
+Source code for *funcsigs* is hosted on `GitHub`_. Any bug reports or feature
+requests can be made using GitHub's `issues system`_.
+
+.. _GitHub: https://github.com/aliles/funcsigs
+.. _issues system: https://github.com/alies/funcsigs/issues
+
+Introspecting callables with the Signature object
+-------------------------------------------------
+
+.. note::
+
+   This section of documentation is a direct repoduction of the Python
+   standard library documentation for the inspect module.
+
+The Signature object represents the call signature of a callable object and its
+return annotation.  To retrieve a Signature object, use the :func:`signature`
+function.
+
+.. function:: signature(callable)
+
+   Return a :class:`Signature` object for the given ``callable``::
+
+      >>> from inspect import signature
+      >>> def foo(a, *, b:int, **kwargs):
+      ...     pass
+
+      >>> sig = signature(foo)
+
+      >>> str(sig)
+      '(a, *, b:int, **kwargs)'
+
+      >>> str(sig.parameters['b'])
+      'b:int'
+
+      >>> sig.parameters['b'].annotation
+      <class 'int'>
+
+   Accepts a wide range of python callables, from plain functions and classes to
+   :func:`functools.partial` objects.
+
+   .. note::
+
+      Some callables may not be introspectable in certain implementations of
+      Python.  For example, in CPython, built-in functions defined in C provide
+      no metadata about their arguments.
+
+
+.. class:: Signature
+
+   A Signature object represents the call signature of a function and its return
+   annotation.  For each parameter accepted by the function it stores a
+   :class:`Parameter` object in its :attr:`parameters` collection.
+
+   Signature objects are *immutable*.  Use :meth:`Signature.replace` to make a
+   modified copy.
+
+   .. attribute:: Signature.empty
+
+      A special class-level marker to specify absence of a return annotation.
+
+   .. attribute:: Signature.parameters
+
+      An ordered mapping of parameters' names to the corresponding
+      :class:`Parameter` objects.
+
+   .. attribute:: Signature.return_annotation
+
+      The "return" annotation for the callable.  If the callable has no "return"
+      annotation, this attribute is set to :attr:`Signature.empty`.
+
+   .. method:: Signature.bind(*args, **kwargs)
+
+      Create a mapping from positional and keyword arguments to parameters.
+      Returns :class:`BoundArguments` if ``*args`` and ``**kwargs`` match the
+      signature, or raises a :exc:`TypeError`.
+
+   .. method:: Signature.bind_partial(*args, **kwargs)
+
+      Works the same way as :meth:`Signature.bind`, but allows the omission of
+      some required arguments (mimics :func:`functools.partial` behavior.)
+      Returns :class:`BoundArguments`, or raises a :exc:`TypeError` if the
+      passed arguments do not match the signature.
+
+   .. method:: Signature.replace(*[, parameters][, return_annotation])
+
+      Create a new Signature instance based on the instance replace was invoked
+      on.  It is possible to pass different ``parameters`` and/or
+      ``return_annotation`` to override the corresponding properties of the base
+      signature.  To remove return_annotation from the copied Signature, pass in
+      :attr:`Signature.empty`.
+
+      ::
+
+         >>> def test(a, b):
+         ...     pass
+         >>> sig = signature(test)
+         >>> new_sig = sig.replace(return_annotation="new return anno")
+         >>> str(new_sig)
+         "(a, b) -> 'new return anno'"
+
+
+.. class:: Parameter
+
+   Parameter objects are *immutable*.  Instead of modifying a Parameter object,
+   you can use :meth:`Parameter.replace` to create a modified copy.
+
+   .. attribute:: Parameter.empty
+
+      A special class-level marker to specify absence of default values and
+      annotations.
+
+   .. attribute:: Parameter.name
+
+      The name of the parameter as a string.  Must be a valid python identifier
+      name (with the exception of ``POSITIONAL_ONLY`` parameters, which can have
+      it set to ``None``).
+
+   .. attribute:: Parameter.default
+
+      The default value for the parameter.  If the parameter has no default
+      value, this attribute is set to :attr:`Parameter.empty`.
+
+   .. attribute:: Parameter.annotation
+
+      The annotation for the parameter.  If the parameter has no annotation,
+      this attribute is set to :attr:`Parameter.empty`.
+
+   .. attribute:: Parameter.kind
+
+      Describes how argument values are bound to the parameter.  Possible values
+      (accessible via :class:`Parameter`, like ``Parameter.KEYWORD_ONLY``):
+
+      +------------------------+----------------------------------------------+
+      |    Name                | Meaning                                      |
+      +========================+==============================================+
+      | *POSITIONAL_ONLY*      | Value must be supplied as a positional       |
+      |                        | argument.                                    |
+      |                        |                                              |
+      |                        | Python has no explicit syntax for defining   |
+      |                        | positional-only parameters, but many built-in|
+      |                        | and extension module functions (especially   |
+      |                        | those that accept only one or two parameters)|
+      |                        | accept them.                                 |
+      +------------------------+----------------------------------------------+
+      | *POSITIONAL_OR_KEYWORD*| Value may be supplied as either a keyword or |
+      |                        | positional argument (this is the standard    |
+      |                        | binding behaviour for functions implemented  |
+      |                        | in Python.)                                  |
+      +------------------------+----------------------------------------------+
+      | *VAR_POSITIONAL*       | A tuple of positional arguments that aren't  |
+      |                        | bound to any other parameter. This           |
+      |                        | corresponds to a ``*args`` parameter in a    |
+      |                        | Python function definition.                  |
+      +------------------------+----------------------------------------------+
+      | *KEYWORD_ONLY*         | Value must be supplied as a keyword argument.|
+      |                        | Keyword only parameters are those which      |
+      |                        | appear after a ``*`` or ``*args`` entry in a |
+      |                        | Python function definition.                  |
+      +------------------------+----------------------------------------------+
+      | *VAR_KEYWORD*          | A dict of keyword arguments that aren't bound|
+      |                        | to any other parameter. This corresponds to a|
+      |                        | ``**kwargs`` parameter in a Python function  |
+      |                        | definition.                                  |
+      +------------------------+----------------------------------------------+
+
+      Example: print all keyword-only arguments without default values::
+
+         >>> def foo(a, b, *, c, d=10):
+         ...     pass
+
+         >>> sig = signature(foo)
+         >>> for param in sig.parameters.values():
+         ...     if (param.kind == param.KEYWORD_ONLY and
+         ...                        param.default is param.empty):
+         ...         print('Parameter:', param)
+         Parameter: c
+
+   .. method:: Parameter.replace(*[, name][, kind][, default][, annotation])
+
+      Create a new Parameter instance based on the instance replaced was invoked
+      on.  To override a :class:`Parameter` attribute, pass the corresponding
+      argument.  To remove a default value or/and an annotation from a
+      Parameter, pass :attr:`Parameter.empty`.
+
+      ::
+
+         >>> from inspect import Parameter
+         >>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42)
+         >>> str(param)
+         'foo=42'
+
+         >>> str(param.replace()) # Will create a shallow copy of 'param'
+         'foo=42'
+
+         >>> str(param.replace(default=Parameter.empty, annotation='spam'))
+         "foo:'spam'"
+
+
+.. class:: BoundArguments
+
+   Result of a :meth:`Signature.bind` or :meth:`Signature.bind_partial` call.
+   Holds the mapping of arguments to the function's parameters.
+
+   .. attribute:: BoundArguments.arguments
+
+      An ordered, mutable mapping (:class:`collections.OrderedDict`) of
+      parameters' names to arguments' values.  Contains only explicitly bound
+      arguments.  Changes in :attr:`arguments` will reflect in :attr:`args` and
+      :attr:`kwargs`.
+
+      Should be used in conjunction with :attr:`Signature.parameters` for any
+      argument processing purposes.
+
+      .. note::
+
+         Arguments for which :meth:`Signature.bind` or
+         :meth:`Signature.bind_partial` relied on a default value are skipped.
+         However, if needed, it is easy to include them.
+
+      ::
+
+        >>> def foo(a, b=10):
+        ...     pass
+
+        >>> sig = signature(foo)
+        >>> ba = sig.bind(5)
+
+        >>> ba.args, ba.kwargs
+        ((5,), {})
+
+        >>> for param in sig.parameters.values():
+        ...     if param.name not in ba.arguments:
+        ...         ba.arguments[param.name] = param.default
+
+        >>> ba.args, ba.kwargs
+        ((5, 10), {})
+
+
+   .. attribute:: BoundArguments.args
+
+      A tuple of positional arguments values.  Dynamically computed from the
+      :attr:`arguments` attribute.
+
+   .. attribute:: BoundArguments.kwargs
+
+      A dict of keyword arguments values.  Dynamically computed from the
+      :attr:`arguments` attribute.
+
+   The :attr:`args` and :attr:`kwargs` properties can be used to invoke
+   functions::
+
+      def test(a, *, b):
+         ...
+
+      sig = signature(test)
+      ba = sig.bind(10, b=20)
+      test(*ba.args, **ba.kwargs)
+
+
+.. seealso::
+
+   :pep:`362` - Function Signature Object.
+      The detailed specification, implementation details and examples.
+
+Copyright
+---------
+
+*funcsigs* is a derived work of CPython under the terms of the `PSF License
+Agreement`_. The original CPython inspect module, its unit tests and
+documentation are the copyright of the Python Software Foundation. The derived
+work is distributed under the `Apache License Version 2.0`_.
+
+.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
+.. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0
diff --git a/funcsigs/__init__.py b/funcsigs/__init__.py
new file mode 100644
index 0000000..fd2f47b
--- /dev/null
+++ b/funcsigs/__init__.py
@@ -0,0 +1,818 @@
+# Copyright 2001-2013 Python Software Foundation; All Rights Reserved
+"""Function signature objects for callables
+
+Back port of Python 3.3's function signature tools from the inspect module,
+modified to be compatible with Python 2.6, 2.7 and 3.2+.
+"""
+from __future__ import absolute_import, division, print_function
+import itertools
+import functools
+import re
+import types
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    from funcsigs.odict import OrderedDict
+
+from funcsigs.version import __version__
+
+__all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
+
+
+_WrapperDescriptor = type(type.__call__)
+_MethodWrapper = type(all.__call__)
+
+_NonUserDefinedCallables = (_WrapperDescriptor,
+                            _MethodWrapper,
+                            types.BuiltinFunctionType)
+
+
+def formatannotation(annotation, base_module=None):
+    if isinstance(annotation, type):
+        if annotation.__module__ in ('builtins', '__builtin__', base_module):
+            return annotation.__name__
+        return annotation.__module__+'.'+annotation.__name__
+    return repr(annotation)
+
+
+def _get_user_defined_method(cls, method_name, *nested):
+    try:
+        if cls is type:
+            return
+        meth = getattr(cls, method_name)
+        for name in nested:
+            meth = getattr(meth, name, meth)
+    except AttributeError:
+        return
+    else:
+        if not isinstance(meth, _NonUserDefinedCallables):
+            # Once '__signature__' will be added to 'C'-level
+            # callables, this check won't be necessary
+            return meth
+
+
+def signature(obj):
+    '''Get a signature object for the passed callable.'''
+
+    if not callable(obj):
+        raise TypeError('{0!r} is not a callable object'.format(obj))
+
+    if isinstance(obj, types.MethodType):
+        sig = signature(obj.__func__)
+        if obj.__self__ is None:
+            # Unbound method: the first parameter becomes positional-only
+            if sig.parameters:
+                first = sig.parameters.values()[0].replace(
+                    kind=_POSITIONAL_ONLY)
+                return sig.replace(
+                    parameters=(first,) + tuple(sig.parameters.values())[1:])
+            else:
+                return sig
+        else:
+            # In this case we skip the first parameter of the underlying
+            # function (usually `self` or `cls`).
+            return sig.replace(parameters=tuple(sig.parameters.values())[1:])
+
+    try:
+        sig = obj.__signature__
+    except AttributeError:
+        pass
+    else:
+        if sig is not None:
+            return sig
+
+    try:
+        # Was this function wrapped by a decorator?
+        wrapped = obj.__wrapped__
+    except AttributeError:
+        pass
+    else:
+        return signature(wrapped)
+
+    if isinstance(obj, types.FunctionType):
+        return Signature.from_function(obj)
+
+    if isinstance(obj, functools.partial):
+        sig = signature(obj.func)
+
+        new_params = OrderedDict(sig.parameters.items())
+
+        partial_args = obj.args or ()
+        partial_keywords = obj.keywords or {}
+        try:
+            ba = sig.bind_partial(*partial_args, **partial_keywords)
+        except TypeError as ex:
+            msg = 'partial object {0!r} has incorrect arguments'.format(obj)
+            raise ValueError(msg)
+
+        for arg_name, arg_value in ba.arguments.items():
+            param = new_params[arg_name]
+            if arg_name in partial_keywords:
+                # We set a new default value, because the following code
+                # is correct:
+                #
+                #   >>> def foo(a): print(a)
+                #   >>> print(partial(partial(foo, a=10), a=20)())
+                #   20
+                #   >>> print(partial(partial(foo, a=10), a=20)(a=30))
+                #   30
+                #
+                # So, with 'partial' objects, passing a keyword argument is
+                # like setting a new default value for the corresponding
+                # parameter
+                #
+                # We also mark this parameter with '_partial_kwarg'
+                # flag.  Later, in '_bind', the 'default' value of this
+                # parameter will be added to 'kwargs', to simulate
+                # the 'functools.partial' real call.
+                new_params[arg_name] = param.replace(default=arg_value,
+                                                     _partial_kwarg=True)
+
+            elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
+                            not param._partial_kwarg):
+                new_params.pop(arg_name)
+
+        return sig.replace(parameters=new_params.values())
+
+    sig = None
+    if isinstance(obj, type):
+        # obj is a class or a metaclass
+
+        # First, let's see if it has an overloaded __call__ defined
+        # in its metaclass
+        call = _get_user_defined_method(type(obj), '__call__')
+        if call is not None:
+            sig = signature(call)
+        else:
+            # Now we check if the 'obj' class has a '__new__' method
+            new = _get_user_defined_method(obj, '__new__')
+            if new is not None:
+                sig = signature(new)
+            else:
+                # Finally, we should have at least __init__ implemented
+                init = _get_user_defined_method(obj, '__init__')
+                if init is not None:
+                    sig = signature(init)
+    elif not isinstance(obj, _NonUserDefinedCallables):
+        # An object with __call__
+        # We also check that the 'obj' is not an instance of
+        # _WrapperDescriptor or _MethodWrapper to avoid
+        # infinite recursion (and even potential segfault)
+        call = _get_user_defined_method(type(obj), '__call__', 'im_func')
+        if call is not None:
+            sig = signature(call)
+
+    if sig is not None:
+        # For classes and objects we skip the first parameter of their
+        # __call__, __new__, or __init__ methods
+        return sig.replace(parameters=tuple(sig.parameters.values())[1:])
+
+    if isinstance(obj, types.BuiltinFunctionType):
+        # Raise a nicer error message for builtins
+        msg = 'no signature found for builtin function {0!r}'.format(obj)
+        raise ValueError(msg)
+
+    raise ValueError('callable {0!r} is not supported by signature'.format(obj))
+
+
+class _void(object):
+    '''A private marker - used in Parameter & Signature'''
+
+
+class _empty(object):
+    pass
+
+
+class _ParameterKind(int):
+    def __new__(self, *args, **kwargs):
+        obj = int.__new__(self, *args)
+        obj._name = kwargs['name']
+        return obj
+
+    def __str__(self):
+        return self._name
+
+    def __repr__(self):
+        return '<_ParameterKind: {0!r}>'.format(self._name)
+
+
+_POSITIONAL_ONLY        = _ParameterKind(0, name='POSITIONAL_ONLY')
+_POSITIONAL_OR_KEYWORD  = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
+_VAR_POSITIONAL         = _ParameterKind(2, name='VAR_POSITIONAL')
+_KEYWORD_ONLY           = _ParameterKind(3, name='KEYWORD_ONLY')
+_VAR_KEYWORD            = _ParameterKind(4, name='VAR_KEYWORD')
+
+
+class Parameter(object):
+    '''Represents a parameter in a function signature.
+
+    Has the following public attributes:
+
+    * name : str
+        The name of the parameter as a string.
+    * default : object
+        The default value for the parameter if specified.  If the
+        parameter has no default value, this attribute is not set.
+    * annotation
+        The annotation for the parameter if specified.  If the
+        parameter has no annotation, this attribute is not set.
+    * kind : str
+        Describes how argument values are bound to the parameter.
+        Possible values: `Parameter.POSITIONAL_ONLY`,
+        `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
+        `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
+    '''
+
+    __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
+
+    POSITIONAL_ONLY         = _POSITIONAL_ONLY
+    POSITIONAL_OR_KEYWORD   = _POSITIONAL_OR_KEYWORD
+    VAR_POSITIONAL          = _VAR_POSITIONAL
+    KEYWORD_ONLY            = _KEYWORD_ONLY
+    VAR_KEYWORD             = _VAR_KEYWORD
+
+    empty = _empty
+
+    def __init__(self, name, kind, default=_empty, annotation=_empty,
+                 _partial_kwarg=False):
+
+        if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
+                        _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
+            raise ValueError("invalid value for 'Parameter.kind' attribute")
+        self._kind = kind
+
+        if default is not _empty:
+            if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
+                msg = '{0} parameters cannot have default values'.format(kind)
+                raise ValueError(msg)
+        self._default = default
+        self._annotation = annotation
+
+        if name is None:
+            if kind != _POSITIONAL_ONLY:
+                raise ValueError("None is not a valid name for a "
+                                 "non-positional-only parameter")
+            self._name = name
+        else:
+            name = str(name)
+            if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
+                msg = '{0!r} is not a valid parameter name'.format(name)
+                raise ValueError(msg)
+            self._name = name
+
+        self._partial_kwarg = _partial_kwarg
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def default(self):
+        return self._default
+
+    @property
+    def annotation(self):
+        return self._annotation
+
+    @property
+    def kind(self):
+        return self._kind
+
+    def replace(self, name=_void, kind=_void, annotation=_void,
+                default=_void, _partial_kwarg=_void):
+        '''Creates a customized copy of the Parameter.'''
+
+        if name is _void:
+            name = self._name
+
+        if kind is _void:
+            kind = self._kind
+
+        if annotation is _void:
+            annotation = self._annotation
+
+        if default is _void:
+            default = self._default
+
+        if _partial_kwarg is _void:
+            _partial_kwarg = self._partial_kwarg
+
+        return type(self)(name, kind, default=default, annotation=annotation,
+                          _partial_kwarg=_partial_kwarg)
+
+    def __str__(self):
+        kind = self.kind
+
+        formatted = self._name
+        if kind == _POSITIONAL_ONLY:
+            if formatted is None:
+                formatted = ''
+            formatted = '<{0}>'.format(formatted)
+
+        # Add annotation and default value
+        if self._annotation is not _empty:
+            formatted = '{0}:{1}'.format(formatted,
+                                       formatannotation(self._annotation))
+
+        if self._default is not _empty:
+            formatted = '{0}={1}'.format(formatted, repr(self._default))
+
+        if kind == _VAR_POSITIONAL:
+            formatted = '*' + formatted
+        elif kind == _VAR_KEYWORD:
+            formatted = '**' + formatted
+
+        return formatted
+
+    def __repr__(self):
+        return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
+                                           id(self), self.name)
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        return (issubclass(other.__class__, Parameter) and
+                self._name == other._name and
+                self._kind == other._kind and
+                self._default == other._default and
+                self._annotation == other._annotation)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class BoundArguments(object):
+    '''Result of `Signature.bind` call.  Holds the mapping of arguments
+    to the function's parameters.
+
+    Has the following public attributes:
+
+    * arguments : OrderedDict
+        An ordered mutable mapping of parameters' names to arguments' values.
+        Does not contain arguments' default values.
+    * signature : Signature
+        The Signature object that created this instance.
+    * args : tuple
+        Tuple of positional arguments values.
+    * kwargs : dict
+        Dict of keyword arguments values.
+    '''
+
+    def __init__(self, signature, arguments):
+        self.arguments = arguments
+        self._signature = signature
+
+    @property
+    def signature(self):
+        return self._signature
+
+    @property
+    def args(self):
+        args = []
+        for param_name, param in self._signature.parameters.items():
+            if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
+                                                    param._partial_kwarg):
+                # Keyword arguments mapped by 'functools.partial'
+                # (Parameter._partial_kwarg is True) are mapped
+                # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
+                # KEYWORD_ONLY
+                break
+
+            try:
+                arg = self.arguments[param_name]
+            except KeyError:
+                # We're done here. Other arguments
+                # will be mapped in 'BoundArguments.kwargs'
+                break
+            else:
+                if param.kind == _VAR_POSITIONAL:
+                    # *args
+                    args.extend(arg)
+                else:
+                    # plain argument
+                    args.append(arg)
+
+        return tuple(args)
+
+    @property
+    def kwargs(self):
+        kwargs = {}
+        kwargs_started = False
+        for param_name, param in self._signature.parameters.items():
+            if not kwargs_started:
+                if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
+                                                param._partial_kwarg):
+                    kwargs_started = True
+                else:
+                    if param_name not in self.arguments:
+                        kwargs_started = True
+                        continue
+
+            if not kwargs_started:
+                continue
+
+            try:
+                arg = self.arguments[param_name]
+            except KeyError:
+                pass
+            else:
+                if param.kind == _VAR_KEYWORD:
+                    # **kwargs
+                    kwargs.update(arg)
+                else:
+                    # plain keyword argument
+                    kwargs[param_name] = arg
+
+        return kwargs
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        return (issubclass(other.__class__, BoundArguments) and
+                self.signature == other.signature and
+                self.arguments == other.arguments)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class Signature(object):
+    '''A Signature object represents the overall signature of a function.
+    It stores a Parameter object for each parameter accepted by the
+    function, as well as information specific to the function itself.
+
+    A Signature object has the following public attributes and methods:
+
+    * parameters : OrderedDict
+        An ordered mapping of parameters' names to the corresponding
+        Parameter objects (keyword-only arguments are in the same order
+        as listed in `code.co_varnames`).
+    * return_annotation : object
+        The annotation for the return type of the function if specified.
+        If the function has no annotation for its return type, this
+        attribute is not set.
+    * bind(*args, **kwargs) -> BoundArguments
+        Creates a mapping from positional and keyword arguments to
+        parameters.
+    * bind_partial(*args, **kwargs) -> BoundArguments
+        Creates a partial mapping from positional and keyword arguments
+        to parameters (simulating 'functools.partial' behavior.)
+    '''
+
+    __slots__ = ('_return_annotation', '_parameters')
+
+    _parameter_cls = Parameter
+    _bound_arguments_cls = BoundArguments
+
+    empty = _empty
+
+    def __init__(self, parameters=None, return_annotation=_empty,
+                 __validate_parameters__=True):
+        '''Constructs Signature from the given list of Parameter
+        objects and 'return_annotation'.  All arguments are optional.
+        '''
+
+        if parameters is None:
+            params = OrderedDict()
+        else:
+            if __validate_parameters__:
+                params = OrderedDict()
+                top_kind = _POSITIONAL_ONLY
+
+                for idx, param in enumerate(parameters):
+                    kind = param.kind
+                    if kind < top_kind:
+                        msg = 'wrong parameter order: {0} before {1}'
+                        msg = msg.format(top_kind, param.kind)
+                        raise ValueError(msg)
+                    else:
+                        top_kind = kind
+
+                    name = param.name
+                    if name is None:
+                        name = str(idx)
+                        param = param.replace(name=name)
+
+                    if name in params:
+                        msg = 'duplicate parameter name: {0!r}'.format(name)
+                        raise ValueError(msg)
+                    params[name] = param
+            else:
+                params = OrderedDict(((param.name, param)
+                                                for param in parameters))
+
+        self._parameters = params
+        self._return_annotation = return_annotation
+
+    @classmethod
+    def from_function(cls, func):
+        '''Constructs Signature for the given python function'''
+
+        if not isinstance(func, types.FunctionType):
+            raise TypeError('{0!r} is not a Python function'.format(func))
+
+        Parameter = cls._parameter_cls
+
+        # Parameter information.
+        func_code = func.__code__
+        pos_count = func_code.co_argcount
+        arg_names = func_code.co_varnames
+        positional = tuple(arg_names[:pos_count])
+        keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
+        keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
+        annotations = getattr(func, '__annotations__', {})
+        defaults = func.__defaults__
+        kwdefaults = getattr(func, '__kwdefaults__', None)
+
+        if defaults:
+            pos_default_count = len(defaults)
+        else:
+            pos_default_count = 0
+
+        parameters = []
+
+        # Non-keyword-only parameters w/o defaults.
+        non_default_count = pos_count - pos_default_count
+        for name in positional[:non_default_count]:
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_POSITIONAL_OR_KEYWORD))
+
+        # ... w/ defaults.
+        for offset, name in enumerate(positional[non_default_count:]):
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_POSITIONAL_OR_KEYWORD,
+                                        default=defaults[offset]))
+
+        # *args
+        if func_code.co_flags & 0x04:
+            name = arg_names[pos_count + keyword_only_count]
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_VAR_POSITIONAL))
+
+        # Keyword-only parameters.
+        for name in keyword_only:
+            default = _empty
+            if kwdefaults is not None:
+                default = kwdefaults.get(name, _empty)
+
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_KEYWORD_ONLY,
+                                        default=default))
+        # **kwargs
+        if func_code.co_flags & 0x08:
+            index = pos_count + keyword_only_count
+            if func_code.co_flags & 0x04:
+                index += 1
+
+            name = arg_names[index]
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_VAR_KEYWORD))
+
+        return cls(parameters,
+                   return_annotation=annotations.get('return', _empty),
+                   __validate_parameters__=False)
+
+    @property
+    def parameters(self):
+        try:
+            return types.MappingProxyType(self._parameters)
+        except AttributeError:
+            return OrderedDict(self._parameters.items())
+
+    @property
+    def return_annotation(self):
+        return self._return_annotation
+
+    def replace(self, parameters=_void, return_annotation=_void):
+        '''Creates a customized copy of the Signature.
+        Pass 'parameters' and/or 'return_annotation' arguments
+        to override them in the new copy.
+        '''
+
+        if parameters is _void:
+            parameters = self.parameters.values()
+
+        if return_annotation is _void:
+            return_annotation = self._return_annotation
+
+        return type(self)(parameters,
+                          return_annotation=return_annotation)
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        if (not issubclass(type(other), Signature) or
+                    self.return_annotation != other.return_annotation or
+                    len(self.parameters) != len(other.parameters)):
+            return False
+
+        other_positions = dict((param, idx)
+                           for idx, param in enumerate(other.parameters.keys()))
+
+        for idx, (param_name, param) in enumerate(self.parameters.items()):
+            if param.kind == _KEYWORD_ONLY:
+                try:
+                    other_param = other.parameters[param_name]
+                except KeyError:
+                    return False
+                else:
+                    if param != other_param:
+                        return False
+            else:
+                try:
+                    other_idx = other_positions[param_name]
+                except KeyError:
+                    return False
+                else:
+                    if (idx != other_idx or
+                                    param != other.parameters[param_name]):
+                        return False
+
+        return True
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def _bind(self, args, kwargs, partial=False):
+        '''Private method.  Don't use directly.'''
+
+        arguments = OrderedDict()
+
+        parameters = iter(self.parameters.values())
+        parameters_ex = ()
+        arg_vals = iter(args)
+
+        if partial:
+            # Support for binding arguments to 'functools.partial' objects.
+            # See 'functools.partial' case in 'signature()' implementation
+            # for details.
+            for param_name, param in self.parameters.items():
+                if (param._partial_kwarg and param_name not in kwargs):
+                    # Simulating 'functools.partial' behavior
+                    kwargs[param_name] = param.default
+
+        while True:
+            # Let's iterate through the positional arguments and corresponding
+            # parameters
+            try:
+                arg_val = next(arg_vals)
+            except StopIteration:
+                # No more positional arguments
+                try:
+                    param = next(parameters)
+                except StopIteration:
+                    # No more parameters. That's it. Just need to check that
+                    # we have no `kwargs` after this while loop
+                    break
+                else:
+                    if param.kind == _VAR_POSITIONAL:
+                        # That's OK, just empty *args.  Let's start parsing
+                        # kwargs
+                        break
+                    elif param.name in kwargs:
+                        if param.kind == _POSITIONAL_ONLY:
+                            msg = '{arg!r} parameter is positional only, ' \
+                                  'but was passed as a keyword'
+                            msg = msg.format(arg=param.name)
+                            raise TypeError(msg)
+                        parameters_ex = (param,)
+                        break
+                    elif (param.kind == _VAR_KEYWORD or
+                                                param.default is not _empty):
+                        # That's fine too - we have a default value for this
+                        # parameter.  So, lets start parsing `kwargs`, starting
+                        # with the current parameter
+                        parameters_ex = (param,)
+                        break
+                    else:
+                        if partial:
+                            parameters_ex = (param,)
+                            break
+                        else:
+                            msg = '{arg!r} parameter lacking default value'
+                            msg = msg.format(arg=param.name)
+                            raise TypeError(msg)
+            else:
+                # We have a positional argument to process
+                try:
+                    param = next(parameters)
+                except StopIteration:
+                    raise TypeError('too many positional arguments')
+                else:
+                    if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
+                        # Looks like we have no parameter for this positional
+                        # argument
+                        raise TypeError('too many positional arguments')
+
+                    if param.kind == _VAR_POSITIONAL:
+                        # We have an '*args'-like argument, let's fill it with
+                        # all positional arguments we have left and move on to
+                        # the next phase
+                        values = [arg_val]
+                        values.extend(arg_vals)
+                        arguments[param.name] = tuple(values)
+                        break
+
+                    if param.name in kwargs:
+                        raise TypeError('multiple values for argument '
+                                        '{arg!r}'.format(arg=param.name))
+
+                    arguments[param.name] = arg_val
+
+        # Now, we iterate through the remaining parameters to process
+        # keyword arguments
+        kwargs_param = None
+        for param in itertools.chain(parameters_ex, parameters):
+            if param.kind == _POSITIONAL_ONLY:
+                # This should never happen in case of a properly built
+                # Signature object (but let's have this check here
+                # to ensure correct behaviour just in case)
+                raise TypeError('{arg!r} parameter is positional only, '
+                                'but was passed as a keyword'. \
+                                format(arg=param.name))
+
+            if param.kind == _VAR_KEYWORD:
+                # Memorize that we have a '**kwargs'-like parameter
+                kwargs_param = param
+                continue
+
+            param_name = param.name
+            try:
+                arg_val = kwargs.pop(param_name)
+            except KeyError:
+                # We have no value for this parameter.  It's fine though,
+                # if it has a default value, or it is an '*args'-like
+                # parameter, left alone by the processing of positional
+                # arguments.
+                if (not partial and param.kind != _VAR_POSITIONAL and
+                                                    param.default is _empty):
+                    raise TypeError('{arg!r} parameter lacking default value'. \
+                                    format(arg=param_name))
+
+            else:
+                arguments[param_name] = arg_val
+
+        if kwargs:
+            if kwargs_param is not None:
+                # Process our '**kwargs'-like parameter
+                arguments[kwargs_param.name] = kwargs
+            else:
+                raise TypeError('too many keyword arguments')
+
+        return self._bound_arguments_cls(self, arguments)
+
+    def bind(self, *args, **kwargs):
+        '''Get a BoundArguments object, that maps the passed `args`
+        and `kwargs` to the function's signature.  Raises `TypeError`
+        if the passed arguments can not be bound.
+        '''
+        return self._bind(args, kwargs)
+
+    def bind_partial(self, *args, **kwargs):
+        '''Get a BoundArguments object, that partially maps the
+        passed `args` and `kwargs` to the function's signature.
+        Raises `TypeError` if the passed arguments can not be bound.
+        '''
+        return self._bind(args, kwargs, partial=True)
+
+    def __str__(self):
+        result = []
+        render_kw_only_separator = True
+        for idx, param in enumerate(self.parameters.values()):
+            formatted = str(param)
+
+            kind = param.kind
+            if kind == _VAR_POSITIONAL:
+                # OK, we have an '*args'-like parameter, so we won't need
+                # a '*' to separate keyword-only arguments
+                render_kw_only_separator = False
+            elif kind == _KEYWORD_ONLY and render_kw_only_separator:
+                # We have a keyword-only parameter to render and we haven't
+                # rendered an '*args'-like parameter before, so add a '*'
+                # separator to the parameters list ("foo(arg1, *, arg2)" case)
+                result.append('*')
+                # This condition should be only triggered once, so
+                # reset the flag
+                render_kw_only_separator = False
+
+            result.append(formatted)
+
+        rendered = '({0})'.format(', '.join(result))
+
+        if self.return_annotation is not _empty:
+            anno = formatannotation(self.return_annotation)
+            rendered += ' -> {0}'.format(anno)
+
+        return rendered
diff --git a/funcsigs/odict.py b/funcsigs/odict.py
new file mode 100644
index 0000000..6221e97
--- /dev/null
+++ b/funcsigs/odict.py
@@ -0,0 +1,261 @@
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
+# Passes Python2.7's test suite and incorporates all the latest updates.
+# Copyright 2009 Raymond Hettinger
+# http://code.activestate.com/recipes/576693/
+"Ordered dictionary"
+
+try:
+    from thread import get_ident as _get_ident
+except ImportError:
+    from dummy_thread import get_ident as _get_ident
+
+try:
+    from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+    pass
+
+
+class OrderedDict(dict):
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as for regular dictionaries.
+
+    # The internal self.__map dictionary maps keys to links in a doubly linked list.
+    # The circular doubly linked list starts and ends with a sentinel element.
+    # The sentinel element never gets deleted (this simplifies the algorithm).
+    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
+
+    def __init__(self, *args, **kwds):
+        '''Initialize an ordered dictionary.  Signature is the same as for
+        regular dictionaries, but keyword arguments are not recommended
+        because their insertion order is arbitrary.
+
+        '''
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = []                     # sentinel node
+            root[:] = [root, root, None]
+            self.__map = {}
+        self.__update(*args, **kwds)
+
+    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link which goes at the end of the linked
+        # list, and the inherited dictionary is updated with the new key/value pair.
+        if key not in self:
+            root = self.__root
+            last = root[0]
+            last[1] = root[0] = self.__map[key] = [last, root, key]
+        dict_setitem(self, key, value)
+
+    def __delitem__(self, key, dict_delitem=dict.__delitem__):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which is
+        # then removed by updating the links in the predecessor and successor nodes.
+        dict_delitem(self, key)
+        link_prev, link_next, key = self.__map.pop(key)
+        link_prev[1] = link_next
+        link_next[0] = link_prev
+
+    def __iter__(self):
+        'od.__iter__() <==> iter(od)'
+        root = self.__root
+        curr = root[1]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[1]
+
+    def __reversed__(self):
+        'od.__reversed__() <==> reversed(od)'
+        root = self.__root
+        curr = root[0]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[0]
+
+    def clear(self):
+        'od.clear() -> None.  Remove all items from od.'
+        try:
+            for node in self.__map.itervalues():
+                del node[:]
+            root = self.__root
+            root[:] = [root, root, None]
+            self.__map.clear()
+        except AttributeError:
+            pass
+        dict.clear(self)
+
+    def popitem(self, last=True):
+        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        '''
+        if not self:
+            raise KeyError('dictionary is empty')
+        root = self.__root
+        if last:
+            link = root[0]
+            link_prev = link[0]
+            link_prev[1] = root
+            root[0] = link_prev
+        else:
+            link = root[1]
+            link_next = link[1]
+            root[1] = link_next
+            link_next[0] = root
+        key = link[2]
+        del self.__map[key]
+        value = dict.pop(self, key)
+        return key, value
+
+    # -- the following methods do not depend on the internal structure --
+
+    def keys(self):
+        'od.keys() -> list of keys in od'
+        return list(self)
+
+    def values(self):
+        'od.values() -> list of values in od'
+        return [self[key] for key in self]
+
+    def items(self):
+        'od.items() -> list of (key, value) pairs in od'
+        return [(key, self[key]) for key in self]
+
+    def iterkeys(self):
+        'od.iterkeys() -> an iterator over the keys in od'
+        return iter(self)
+
+    def itervalues(self):
+        'od.itervalues -> an iterator over the values in od'
+        for k in self:
+            yield self[k]
+
+    def iteritems(self):
+        'od.iteritems -> an iterator over the (key, value) items in od'
+        for k in self:
+            yield (k, self[k])
+
+    def update(*args, **kwds):
+        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.
+
+        If E is a dict instance, does:           for k in E: od[k] = E[k]
+        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
+        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
+        In either case, this is followed by:     for k, v in F.items(): od[k] = v
+
+        '''
+        if len(args) > 2:
+            raise TypeError('update() takes at most 2 positional '
+                            'arguments (%d given)' % (len(args),))
+        elif not args:
+            raise TypeError('update() takes at least 1 argument (0 given)')
+        self = args[0]
+        # Make progressively weaker assumptions about "other"
+        other = ()
+        if len(args) == 2:
+            other = args[1]
+        if isinstance(other, dict):
+            for key in other:
+                self[key] = other[key]
+        elif hasattr(other, 'keys'):
+            for key in other.keys():
+                self[key] = other[key]
+        else:
+            for key, value in other:
+                self[key] = value
+        for key, value in kwds.items():
+            self[key] = value
+
+    __update = update  # let subclasses override update without breaking __init__
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        If key is not found, d is returned if given, otherwise KeyError is raised.
+
+        '''
+        if key in self:
+            result = self[key]
+            del self[key]
+            return result
+        if default is self.__marker:
+            raise KeyError(key)
+        return default
+
+    def setdefault(self, key, default=None):
+        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        if key in self:
+            return self[key]
+        self[key] = default
+        return default
+
+    def __repr__(self, _repr_running={}):
+        'od.__repr__() <==> repr(od)'
+        call_key = id(self), _get_ident()
+        if call_key in _repr_running:
+            return '...'
+        _repr_running[call_key] = 1
+        try:
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+        finally:
+            del _repr_running[call_key]
+
+    def __reduce__(self):
+        'Return state information for pickling'
+        items = [[k, self[k]] for k in self]
+        inst_dict = vars(self).copy()
+        for k in vars(OrderedDict()):
+            inst_dict.pop(k, None)
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def copy(self):
+        'od.copy() -> a shallow copy of od'
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        and values equal to v (which defaults to None).
+
+        '''
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
+        if isinstance(other, OrderedDict):
+            return len(self)==len(other) and self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
+
+    # -- the following methods are only used in Python 2.7 --
+
+    def viewkeys(self):
+        "od.viewkeys() -> a set-like object providing a view on od's keys"
+        return KeysView(self)
+
+    def viewvalues(self):
+        "od.viewvalues() -> an object providing a view on od's values"
+        return ValuesView(self)
+
+    def viewitems(self):
+        "od.viewitems() -> a set-like object providing a view on od's items"
+        return ItemsView(self)
diff --git a/funcsigs/version.py b/funcsigs/version.py
new file mode 100644
index 0000000..896a370
--- /dev/null
+++ b/funcsigs/version.py
@@ -0,0 +1 @@
+__version__ = "0.4"
diff --git a/requirements/development.txt b/requirements/development.txt
new file mode 100644
index 0000000..ecafb0a
--- /dev/null
+++ b/requirements/development.txt
@@ -0,0 +1,6 @@
+coverage
+coveralls
+pip
+flake8
+sphinx
+wheel
diff --git a/requirements/production.txt b/requirements/production.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/requirements/production.txt
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..5e40900
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[wheel]
+universal = 1
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..98b0912
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+from setuptools import setup
+import re
+import sys
+
+def load_version(filename='funcsigs/version.py'):
+    "Parse a __version__ number from a source file"
+    with open(filename) as source:
+        text = source.read()
+        match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", text)
+        if not match:
+            msg = "Unable to find version number in {}".format(filename)
+            raise RuntimeError(msg)
+        version = match.group(1)
+        return version
+
+def load_rst(filename='docs/source/guide_content.rst'):
+    "Purge refs directives from restructured text"
+    with open(filename) as source:
+        text = source.read()
+        doc = re.sub(r':\w+:`~?([a-zA-Z._()]+)`', r'*\1*', text)
+        return doc
+
+setup(
+    name="funcsigs",
+    version=load_version(),
+    packages=['funcsigs'],
+    zip_safe=False,
+    author="Aaron Iles",
+    author_email="aaron.iles@gmail.com",
+    url="http://funcsigs.readthedocs.org",
+    description="Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+",
+    long_description=open('README.rst').read(),
+    # long_description=load_rst(),
+    license="ASL",
+    install_requires = [],
+    classifiers = [
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Apache Software License',
+        '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 :: Implementation :: CPython',
+        'Programming Language :: Python :: Implementation :: PyPy',
+        'Topic :: Software Development :: Libraries :: Python Modules'
+    ],
+    tests_require = [] if sys.version_info[0] > 2 else ['unittest2'],
+    test_suite = "tests" if sys.version_info[0] > 2 else 'unittest2.collector'
+)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/test_formatannotation.py b/tests/test_formatannotation.py
new file mode 100644
index 0000000..fd7a887
--- /dev/null
+++ b/tests/test_formatannotation.py
@@ -0,0 +1,27 @@
+try:
+    # python 2.x
+    import unittest2 as unittest
+except ImportError:
+    # python 3.x
+    import unittest
+
+import funcsigs
+
+
+class TestFormatAnnotation(unittest.TestCase):
+    def test_string (self):
+        self.assertEqual(funcsigs.formatannotation("annotation"),
+                         "'annotation'")
+
+    def test_builtin_type (self):
+        self.assertEqual(funcsigs.formatannotation(int),
+                         "int")
+
+    def test_user_type (self):
+        class dummy (object): pass
+        self.assertEqual(funcsigs.formatannotation(dummy),
+                         "tests.test_formatannotation.dummy")
+
+
+if __name__ == "__main__":
+    unittest.begin()
diff --git a/tests/test_funcsigs.py b/tests/test_funcsigs.py
new file mode 100644
index 0000000..eecc0a8
--- /dev/null
+++ b/tests/test_funcsigs.py
@@ -0,0 +1,93 @@
+try:
+    # python 2.x
+    import unittest2 as unittest
+except ImportError:
+    # python 3.x
+    import unittest
+
+import doctest
+import sys
+
+import funcsigs as inspect
+
+
+class TestFunctionSignatures(unittest.TestCase):
+
+    @staticmethod
+    def signature(func):
+        sig = inspect.signature(func)
+        return (tuple((param.name,
+                       (Ellipsis if param.default is param.empty else param.default),
+                       (Ellipsis if param.annotation is param.empty
+                                                        else param.annotation),
+                       str(param.kind).lower())
+                                    for param in sig.parameters.values()),
+                (Ellipsis if sig.return_annotation is sig.empty
+                                            else sig.return_annotation))
+
+    def test_zero_arguments(self):
+        def test():
+            pass
+        self.assertEqual(self.signature(test),
+                ((), Ellipsis))
+
+    def test_single_positional_argument(self):
+        def test(a):
+            pass
+        self.assertEqual(self.signature(test),
+                (((('a', Ellipsis, Ellipsis, "positional_or_keyword")),), Ellipsis))
+
+    def test_single_keyword_argument(self):
+        def test(a=None):
+            pass
+        self.assertEqual(self.signature(test),
+                (((('a', None, Ellipsis, "positional_or_keyword")),), Ellipsis))
+
+    def test_var_args(self):
+        def test(*args):
+            pass
+        self.assertEqual(self.signature(test),
+                (((('args', Ellipsis, Ellipsis, "var_positional")),), Ellipsis))
+
+    def test_keywords_args(self):
+        def test(**kwargs):
+            pass
+        self.assertEqual(self.signature(test),
+                (((('kwargs', Ellipsis, Ellipsis, "var_keyword")),), Ellipsis))
+
+    def test_multiple_arguments(self):
+        def test(a, b=None, *args, **kwargs):
+            pass
+        self.assertEqual(self.signature(test), ((
+            ('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+            ('b', None, Ellipsis, "positional_or_keyword"),
+            ('args', Ellipsis, Ellipsis, "var_positional"),
+            ('kwargs', Ellipsis, Ellipsis, "var_keyword"),
+            ), Ellipsis))
+
+    def test_has_version(self):
+        self.assertTrue(inspect.__version__)
+
+    def test_readme(self):
+        doctest.testfile('../README.rst')
+
+    def test_unbound_method(self):
+        if sys.version_info < (3,):
+            self_kind = "positional_only"
+        else:
+            self_kind = "positional_or_keyword"
+        class Test(object):
+            def method(self):
+                pass
+            def method_with_args(self, a):
+                pass
+        self.assertEqual(self.signature(Test.method),
+                (((('self', Ellipsis, Ellipsis, self_kind)),), Ellipsis))
+        self.assertEqual(self.signature(Test.method_with_args), ((
+                ('self', Ellipsis, Ellipsis, self_kind),
+                ('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                ), Ellipsis))
+
+
+if __name__ == "__main__":
+    unittest.begin()
diff --git a/tests/test_inspect.py b/tests/test_inspect.py
new file mode 100644
index 0000000..323c323
--- /dev/null
+++ b/tests/test_inspect.py
@@ -0,0 +1,1019 @@
+# Copyright 2001-2013 Python Software Foundation; All Rights Reserved
+from __future__ import absolute_import, division, print_function
+import collections
+import sys
+
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
+
+import funcsigs as inspect
+
+
+class TestSignatureObject(unittest.TestCase):
+    @staticmethod
+    def signature(func):
+        sig = inspect.signature(func)
+        return (tuple((param.name,
+                       (Ellipsis if param.default is param.empty else param.default),
+                       (Ellipsis if param.annotation is param.empty
+                                                        else param.annotation),
+                       str(param.kind).lower())
+                                    for param in sig.parameters.values()),
+                (Ellipsis if sig.return_annotation is sig.empty
+                                            else sig.return_annotation))
+
+    def __init__(self, *args, **kwargs):
+        unittest.TestCase.__init__(self, *args, **kwargs)
+        if not hasattr(self, 'assertRaisesRegex'):
+            self.assertRaisesRegex = self.assertRaisesRegexp
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_object(self):
+    S = inspect.Signature
+    P = inspect.Parameter
+
+    self.assertEqual(str(S()), '()')
+
+    def test(po, pk, *args, ko, **kwargs):
+        pass
+    sig = inspect.signature(test)
+    po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY)
+    pk = sig.parameters['pk']
+    args = sig.parameters['args']
+    ko = sig.parameters['ko']
+    kwargs = sig.parameters['kwargs']
+
+    S((po, pk, args, ko, kwargs))
+
+    with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
+        S((pk, po, args, ko, kwargs))
+
+    with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
+        S((po, args, pk, ko, kwargs))
+
+    with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
+        S((args, po, pk, ko, kwargs))
+
+    with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
+        S((po, pk, args, kwargs, ko))
+
+    kwargs2 = kwargs.replace(name='args')
+    with self.assertRaisesRegex(ValueError, 'duplicate parameter name'):
+        S((po, pk, args, kwargs2, ko))
+""")
+
+    def test_signature_immutability(self):
+        def test(a):
+            pass
+        sig = inspect.signature(test)
+
+        with self.assertRaises(AttributeError):
+            sig.foo = 'bar'
+
+        # Python2 does not have MappingProxyType class
+        if sys.version_info[:2] < (3, 3):
+            return
+
+        with self.assertRaises(TypeError):
+            sig.parameters['a'] = None
+
+    def test_signature_on_noarg(self):
+        def test():
+            pass
+        self.assertEqual(self.signature(test), ((), Ellipsis))
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_wargs(self):
+    def test(a, b:'foo') -> 123:
+        pass
+    self.assertEqual(self.signature(test),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, 'foo', "positional_or_keyword")),
+                      123))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_wkwonly(self):
+    def test(*, a:float, b:str) -> int:
+        pass
+    self.assertEqual(self.signature(test),
+                     ((('a', Ellipsis, float, "keyword_only"),
+                       ('b', Ellipsis, str, "keyword_only")),
+                       int))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_complex_args(self):
+    def test(a, b:'foo'=10, *args:'bar', spam:'baz', ham=123, **kwargs:int):
+        pass
+    self.assertEqual(self.signature(test),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', 10, 'foo', "positional_or_keyword"),
+                       ('args', Ellipsis, 'bar', "var_positional"),
+                       ('spam', Ellipsis, 'baz', "keyword_only"),
+                       ('ham', 123, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, int, "var_keyword")),
+                      Ellipsis))
+""")
+
+    def test_signature_on_builtin_function(self):
+        with self.assertRaisesRegex(ValueError, 'not supported by signature'):
+            inspect.signature(type)
+        with self.assertRaisesRegex(ValueError, 'not supported by signature'):
+            # support for 'wrapper_descriptor'
+            inspect.signature(type.__call__)
+            if hasattr(sys, 'pypy_version_info'):
+                raise ValueError('not supported by signature')
+        with self.assertRaisesRegex(ValueError, 'not supported by signature'):
+            # support for 'method-wrapper'
+            inspect.signature(min.__call__)
+            if hasattr(sys, 'pypy_version_info'):
+                raise ValueError('not supported by signature')
+        with self.assertRaisesRegex(ValueError,
+                                     'no signature found for builtin function'):
+            # support for 'method-wrapper'
+            inspect.signature(min)
+
+    def test_signature_on_non_function(self):
+        with self.assertRaisesRegex(TypeError, 'is not a callable object'):
+            inspect.signature(42)
+
+        with self.assertRaisesRegex(TypeError, 'is not a Python function'):
+            inspect.Signature.from_function(42)
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_method(self):
+    class Test:
+        def foo(self, arg1, arg2=1) -> int:
+            pass
+
+    meth = Test().foo
+
+    self.assertEqual(self.signature(meth),
+                     ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg2', 1, Ellipsis, "positional_or_keyword")),
+                      int))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_classmethod(self):
+    class Test:
+        @classmethod
+        def foo(cls, arg1, *, arg2=1):
+            pass
+
+    meth = Test().foo
+    self.assertEqual(self.signature(meth),
+                     ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg2', 1, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    meth = Test.foo
+    self.assertEqual(self.signature(meth),
+                     ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg2', 1, Ellipsis, "keyword_only")),
+                      Ellipsis))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_staticmethod(self):
+    class Test:
+        @staticmethod
+        def foo(cls, *, arg):
+            pass
+
+    meth = Test().foo
+    self.assertEqual(self.signature(meth),
+                     ((('cls', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    meth = Test.foo
+    self.assertEqual(self.signature(meth),
+                     ((('cls', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_partial(self):
+    from functools import partial
+
+    def test():
+        pass
+
+    self.assertEqual(self.signature(partial(test)), ((), Ellipsis))
+
+    with self.assertRaisesRegex(ValueError, "has incorrect arguments"):
+        inspect.signature(partial(test, 1))
+
+    with self.assertRaisesRegex(ValueError, "has incorrect arguments"):
+        inspect.signature(partial(test, a=1))
+
+    def test(a, b, *, c, d):
+        pass
+
+    self.assertEqual(self.signature(partial(test)),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('c', Ellipsis, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 1)),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('c', Ellipsis, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 1, c=2)),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('c', 2, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, b=1, c=2)),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', 1, Ellipsis, "positional_or_keyword"),
+                       ('c', 2, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 0, b=1, c=2)),
+                     ((('b', 1, Ellipsis, "positional_or_keyword"),
+                       ('c', 2, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only"),),
+                      Ellipsis))
+
+    def test(a, *args, b, **kwargs):
+        pass
+
+    self.assertEqual(self.signature(partial(test, 1)),
+                     ((('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', Ellipsis, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 1, 2, 3)),
+                     ((('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', Ellipsis, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+
+    self.assertEqual(self.signature(partial(test, 1, 2, 3, test=True)),
+                     ((('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', Ellipsis, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 1, 2, 3, test=1, b=0)),
+                     ((('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', 0, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, b=0)),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', 0, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, b=0, test=1)),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', 0, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    def test(a, b, c:int) -> 42:
+        pass
+
+    sig = test.__signature__ = inspect.signature(test)
+
+    self.assertEqual(self.signature(partial(partial(test, 1))),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('c', Ellipsis, int, "positional_or_keyword")),
+                      42))
+
+    self.assertEqual(self.signature(partial(partial(test, 1), 2)),
+                     ((('c', Ellipsis, int, "positional_or_keyword"),),
+                      42))
+
+    psig = inspect.signature(partial(partial(test, 1), 2))
+
+    def foo(a):
+        return a
+    _foo = partial(partial(foo, a=10), a=20)
+    self.assertEqual(self.signature(_foo),
+                     ((('a', 20, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+    # check that we don't have any side-effects in signature(),
+    # and the partial object is still functioning
+    self.assertEqual(_foo(), 20)
+
+    def foo(a, b, c):
+        return a, b, c
+    _foo = partial(partial(foo, 1, b=20), b=30)
+    self.assertEqual(self.signature(_foo),
+                     ((('b', 30, Ellipsis, "positional_or_keyword"),
+                       ('c', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+    self.assertEqual(_foo(c=10), (1, 30, 10))
+    _foo = partial(_foo, 2) # now 'b' has two values -
+                            # positional and keyword
+    with self.assertRaisesRegex(ValueError, "has incorrect arguments"):
+        inspect.signature(_foo)
+
+    def foo(a, b, c, *, d):
+        return a, b, c, d
+    _foo = partial(partial(foo, d=20, c=20), b=10, d=30)
+    self.assertEqual(self.signature(_foo),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', 10, Ellipsis, "positional_or_keyword"),
+                       ('c', 20, Ellipsis, "positional_or_keyword"),
+                       ('d', 30, Ellipsis, "keyword_only")),
+                      Ellipsis))
+    ba = inspect.signature(_foo).bind(a=200, b=11)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (200, 11, 20, 30))
+
+    def foo(a=1, b=2, c=3):
+        return a, b, c
+    _foo = partial(foo, a=10, c=13)
+    ba = inspect.signature(_foo).bind(11)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 2, 13))
+    ba = inspect.signature(_foo).bind(11, 12)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13))
+    ba = inspect.signature(_foo).bind(11, b=12)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13))
+    ba = inspect.signature(_foo).bind(b=12)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (10, 12, 13))
+    _foo = partial(_foo, b=10)
+    ba = inspect.signature(_foo).bind(12, 14)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (12, 14, 13))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_decorated(self):
+    import functools
+
+    def decorator(func):
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs) -> int:
+            return func(*args, **kwargs)
+        return wrapper
+
+    class Foo:
+        @decorator
+        def bar(self, a, b):
+            pass
+
+    self.assertEqual(self.signature(Foo.bar),
+                     ((('self', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(Foo().bar),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+
+    # Test that we handle method wrappers correctly
+    def decorator(func):
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs) -> int:
+            return func(42, *args, **kwargs)
+        sig = inspect.signature(func)
+        new_params = tuple(sig.parameters.values())[1:]
+        wrapper.__signature__ = sig.replace(parameters=new_params)
+        return wrapper
+
+    class Foo:
+        @decorator
+        def __call__(self, a, b):
+            pass
+
+    self.assertEqual(self.signature(Foo.__call__),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(Foo().__call__),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_class(self):
+    class C:
+        def __init__(self, a):
+            pass
+
+    self.assertEqual(self.signature(C),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+
+    class CM(type):
+        def __call__(cls, a):
+            pass
+    class C(metaclass=CM):
+        def __init__(self, b):
+            pass
+
+    self.assertEqual(self.signature(C),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+
+    class CM(type):
+        def __new__(mcls, name, bases, dct, *, foo=1):
+            return super().__new__(mcls, name, bases, dct)
+    class C(metaclass=CM):
+        def __init__(self, b):
+            pass
+
+    self.assertEqual(self.signature(C),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(CM),
+                     ((('name', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bases', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('dct', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('foo', 1, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    class CMM(type):
+        def __new__(mcls, name, bases, dct, *, foo=1):
+            return super().__new__(mcls, name, bases, dct)
+        def __call__(cls, nm, bs, dt):
+            return type(nm, bs, dt)
+    class CM(type, metaclass=CMM):
+        def __new__(mcls, name, bases, dct, *, bar=2):
+            return super().__new__(mcls, name, bases, dct)
+    class C(metaclass=CM):
+        def __init__(self, b):
+            pass
+
+    self.assertEqual(self.signature(CMM),
+                     ((('name', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bases', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('dct', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('foo', 1, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(CM),
+                     ((('nm', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bs', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('dt', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(C),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+
+    class CM(type):
+        def __init__(cls, name, bases, dct, *, bar=2):
+            return super().__init__(name, bases, dct)
+    class C(metaclass=CM):
+        def __init__(self, b):
+            pass
+
+    self.assertEqual(self.signature(CM),
+                     ((('name', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bases', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('dct', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bar', 2, Ellipsis, "keyword_only")),
+                      Ellipsis))
+""")
+
+    def test_signature_on_callable_objects(self):
+        class Foo(object):
+            def __call__(self, a):
+                pass
+
+        self.assertEqual(self.signature(Foo()),
+                         ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                          Ellipsis))
+
+        class Spam(object):
+            pass
+        with self.assertRaisesRegex(TypeError, "is not a callable object"):
+            inspect.signature(Spam())
+
+        class Bar(Spam, Foo):
+            pass
+
+        self.assertEqual(self.signature(Bar()),
+                         ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                          Ellipsis))
+
+        class ToFail(object):
+            __call__ = type
+        with self.assertRaisesRegex(ValueError, "not supported by signature"):
+            inspect.signature(ToFail())
+
+        if sys.version_info[0] < 3:
+            return
+
+        class Wrapped(object):
+            pass
+        Wrapped.__wrapped__ = lambda a: None
+        self.assertEqual(self.signature(Wrapped),
+                         ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                          Ellipsis))
+
+    def test_signature_on_lambdas(self):
+        self.assertEqual(self.signature((lambda a=10: a)),
+                         ((('a', 10, Ellipsis, "positional_or_keyword"),),
+                          Ellipsis))
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_equality(self):
+    def foo(a, *, b:int) -> float: pass
+    self.assertNotEqual(inspect.signature(foo), 42)
+
+    def bar(a, *, b:int) -> float: pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, *, b:int) -> int: pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, *, b:int): pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, *, b:int=42) -> float: pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, *, c) -> float: pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, b:int) -> float: pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+    def spam(b:int, a) -> float: pass
+    self.assertNotEqual(inspect.signature(spam), inspect.signature(bar))
+
+    def foo(*, a, b, c): pass
+    def bar(*, c, b, a): pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def foo(*, a=1, b, c): pass
+    def bar(*, c, b, a=1): pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def foo(pos, *, a=1, b, c): pass
+    def bar(pos, *, c, b, a=1): pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def foo(pos, *, a, b, c): pass
+    def bar(pos, *, c, b, a=1): pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def foo(pos, *args, a=42, b, c, **kwargs:int): pass
+    def bar(pos, *args, c, b, a=42, **kwargs:int): pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+""")
+
+    def test_signature_unhashable(self):
+        def foo(a): pass
+        sig = inspect.signature(foo)
+        with self.assertRaisesRegex(TypeError, 'unhashable type'):
+            hash(sig)
+
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_str(self):
+    def foo(a:int=1, *, b, c=None, **kwargs) -> 42:
+        pass
+    self.assertEqual(str(inspect.signature(foo)),
+                     '(a:int=1, *, b, c=None, **kwargs) -> 42')
+
+    def foo(a:int=1, *args, b, c=None, **kwargs) -> 42:
+        pass
+    self.assertEqual(str(inspect.signature(foo)),
+                     '(a:int=1, *args, b, c=None, **kwargs) -> 42')
+
+    def foo():
+        pass
+    self.assertEqual(str(inspect.signature(foo)), '()')
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_str_positional_only(self):
+    P = inspect.Parameter
+
+    def test(a_po, *, b, **kwargs):
+        return a_po, kwargs
+
+    sig = inspect.signature(test)
+    new_params = list(sig.parameters.values())
+    new_params[0] = new_params[0].replace(kind=P.POSITIONAL_ONLY)
+    test.__signature__ = sig.replace(parameters=new_params)
+
+    self.assertEqual(str(inspect.signature(test)),
+                     '(<a_po>, *, b, **kwargs)')
+
+    sig = inspect.signature(test)
+    new_params = list(sig.parameters.values())
+    new_params[0] = new_params[0].replace(name=None)
+    test.__signature__ = sig.replace(parameters=new_params)
+    self.assertEqual(str(inspect.signature(test)),
+                     '(<0>, *, b, **kwargs)')
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_replace_anno(self):
+    def test() -> 42:
+        pass
+
+    sig = inspect.signature(test)
+    sig = sig.replace(return_annotation=None)
+    self.assertIs(sig.return_annotation, None)
+    sig = sig.replace(return_annotation=sig.empty)
+    self.assertIs(sig.return_annotation, sig.empty)
+    sig = sig.replace(return_annotation=42)
+    self.assertEqual(sig.return_annotation, 42)
+    self.assertEqual(sig, inspect.signature(test))
+""")
+
+
+class TestParameterObject(unittest.TestCase):
+
+    def __init__(self, *args, **kwargs):
+        unittest.TestCase.__init__(self, *args, **kwargs)
+        if not hasattr(self, 'assertRaisesRegex'):
+            self.assertRaisesRegex = self.assertRaisesRegexp
+
+    def test_signature_parameter_kinds(self):
+        P = inspect.Parameter
+        self.assertTrue(P.POSITIONAL_ONLY < P.POSITIONAL_OR_KEYWORD < \
+                        P.VAR_POSITIONAL < P.KEYWORD_ONLY < P.VAR_KEYWORD)
+
+        self.assertEqual(str(P.POSITIONAL_ONLY), 'POSITIONAL_ONLY')
+        self.assertTrue('POSITIONAL_ONLY' in repr(P.POSITIONAL_ONLY))
+
+    def test_signature_parameter_object(self):
+        p = inspect.Parameter('foo', default=10,
+                              kind=inspect.Parameter.POSITIONAL_ONLY)
+        self.assertEqual(p.name, 'foo')
+        self.assertEqual(p.default, 10)
+        self.assertIs(p.annotation, p.empty)
+        self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
+
+        with self.assertRaisesRegex(ValueError, 'invalid value'):
+            inspect.Parameter('foo', default=10, kind='123')
+
+        with self.assertRaisesRegex(ValueError, 'not a valid parameter name'):
+            inspect.Parameter('1', kind=inspect.Parameter.VAR_KEYWORD)
+
+        with self.assertRaisesRegex(ValueError,
+                                     'non-positional-only parameter'):
+            inspect.Parameter(None, kind=inspect.Parameter.VAR_KEYWORD)
+
+        with self.assertRaisesRegex(ValueError, 'cannot have default values'):
+            inspect.Parameter('a', default=42,
+                              kind=inspect.Parameter.VAR_KEYWORD)
+
+        with self.assertRaisesRegex(ValueError, 'cannot have default values'):
+            inspect.Parameter('a', default=42,
+                              kind=inspect.Parameter.VAR_POSITIONAL)
+
+        p = inspect.Parameter('a', default=42,
+                              kind=inspect.Parameter.POSITIONAL_OR_KEYWORD)
+        with self.assertRaisesRegex(ValueError, 'cannot have default values'):
+            p.replace(kind=inspect.Parameter.VAR_POSITIONAL)
+
+        self.assertTrue(repr(p).startswith('<Parameter'))
+
+    def test_signature_parameter_equality(self):
+        P = inspect.Parameter
+        p = P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY)
+
+        self.assertEqual(p, p)
+        self.assertNotEqual(p, 42)
+
+        self.assertEqual(p, P('foo', default=42,
+                              kind=inspect.Parameter.KEYWORD_ONLY))
+
+    def test_signature_parameter_unhashable(self):
+        p = inspect.Parameter('foo', default=42,
+                              kind=inspect.Parameter.KEYWORD_ONLY)
+
+        with self.assertRaisesRegex(TypeError, 'unhashable type'):
+            hash(p)
+
+    def test_signature_parameter_replace(self):
+        p = inspect.Parameter('foo', default=42,
+                              kind=inspect.Parameter.KEYWORD_ONLY)
+
+        self.assertIsNot(p, p.replace())
+        self.assertEqual(p, p.replace())
+
+        p2 = p.replace(annotation=1)
+        self.assertEqual(p2.annotation, 1)
+        p2 = p2.replace(annotation=p2.empty)
+        self.assertEqual(p, p2)
+
+        p2 = p2.replace(name='bar')
+        self.assertEqual(p2.name, 'bar')
+        self.assertNotEqual(p2, p)
+
+        with self.assertRaisesRegex(ValueError, 'not a valid parameter name'):
+            p2 = p2.replace(name=p2.empty)
+
+        p2 = p2.replace(name='foo', default=None)
+        self.assertIs(p2.default, None)
+        self.assertNotEqual(p2, p)
+
+        p2 = p2.replace(name='foo', default=p2.empty)
+        self.assertIs(p2.default, p2.empty)
+
+
+        p2 = p2.replace(default=42, kind=p2.POSITIONAL_OR_KEYWORD)
+        self.assertEqual(p2.kind, p2.POSITIONAL_OR_KEYWORD)
+        self.assertNotEqual(p2, p)
+
+        with self.assertRaisesRegex(ValueError, 'invalid value for'):
+            p2 = p2.replace(kind=p2.empty)
+
+        p2 = p2.replace(kind=p2.KEYWORD_ONLY)
+        self.assertEqual(p2, p)
+
+    def test_signature_parameter_positional_only(self):
+        p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
+        self.assertEqual(str(p), '<>')
+
+        p = p.replace(name='1')
+        self.assertEqual(str(p), '<1>')
+
+    def test_signature_parameter_immutability(self):
+        p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
+
+        with self.assertRaises(AttributeError):
+            p.foo = 'bar'
+
+        with self.assertRaises(AttributeError):
+            p.kind = 123
+
+
+class TestSignatureBind(unittest.TestCase):
+    @staticmethod
+    def call(func, *args, **kwargs):
+        sig = inspect.signature(func)
+        ba = sig.bind(*args, **kwargs)
+        return func(*ba.args, **ba.kwargs)
+
+    def __init__(self, *args, **kwargs):
+        unittest.TestCase.__init__(self, *args, **kwargs)
+        if not hasattr(self, 'assertRaisesRegex'):
+            self.assertRaisesRegex = self.assertRaisesRegexp
+
+    def test_signature_bind_empty(self):
+        def test():
+            return 42
+
+        self.assertEqual(self.call(test), 42)
+        with self.assertRaisesRegex(TypeError, 'too many positional arguments'):
+            self.call(test, 1)
+        with self.assertRaisesRegex(TypeError, 'too many positional arguments'):
+            self.call(test, 1, spam=10)
+        with self.assertRaisesRegex(TypeError, 'too many keyword arguments'):
+            self.call(test, spam=1)
+
+    def test_signature_bind_var(self):
+        def test(*args, **kwargs):
+            return args, kwargs
+
+        self.assertEqual(self.call(test), ((), {}))
+        self.assertEqual(self.call(test, 1), ((1,), {}))
+        self.assertEqual(self.call(test, 1, 2), ((1, 2), {}))
+        self.assertEqual(self.call(test, foo='bar'), ((), {'foo': 'bar'}))
+        self.assertEqual(self.call(test, 1, foo='bar'), ((1,), {'foo': 'bar'}))
+        self.assertEqual(self.call(test, args=10), ((), {'args': 10}))
+        self.assertEqual(self.call(test, 1, 2, foo='bar'),
+                         ((1, 2), {'foo': 'bar'}))
+
+    def test_signature_bind_just_args(self):
+        def test(a, b, c):
+            return a, b, c
+
+        self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3))
+
+        with self.assertRaisesRegex(TypeError, 'too many positional arguments'):
+            self.call(test, 1, 2, 3, 4)
+
+        with self.assertRaisesRegex(TypeError, "'b' parameter lacking default"):
+            self.call(test, 1)
+
+        with self.assertRaisesRegex(TypeError, "'a' parameter lacking default"):
+            self.call(test)
+
+        def test(a, b, c=10):
+            return a, b, c
+        self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3))
+        self.assertEqual(self.call(test, 1, 2), (1, 2, 10))
+
+        def test(a=1, b=2, c=3):
+            return a, b, c
+        self.assertEqual(self.call(test, a=10, c=13), (10, 2, 13))
+        self.assertEqual(self.call(test, a=10), (10, 2, 3))
+        self.assertEqual(self.call(test, b=10), (1, 10, 3))
+
+    def test_signature_bind_varargs_order(self):
+        def test(*args):
+            return args
+
+        self.assertEqual(self.call(test), ())
+        self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3))
+
+    def test_signature_bind_args_and_varargs(self):
+        def test(a, b, c=3, *args):
+            return a, b, c, args
+
+        self.assertEqual(self.call(test, 1, 2, 3, 4, 5), (1, 2, 3, (4, 5)))
+        self.assertEqual(self.call(test, 1, 2), (1, 2, 3, ()))
+        self.assertEqual(self.call(test, b=1, a=2), (2, 1, 3, ()))
+        self.assertEqual(self.call(test, 1, b=2), (1, 2, 3, ()))
+
+        with self.assertRaisesRegex(TypeError,
+                                     "multiple values for argument 'c'"):
+            self.call(test, 1, 2, 3, c=4)
+
+    def test_signature_bind_just_kwargs(self):
+        def test(**kwargs):
+            return kwargs
+
+        self.assertEqual(self.call(test), {})
+        self.assertEqual(self.call(test, foo='bar', spam='ham'),
+                         {'foo': 'bar', 'spam': 'ham'})
+
+    def test_signature_bind_args_and_kwargs(self):
+        def test(a, b, c=3, **kwargs):
+            return a, b, c, kwargs
+
+        self.assertEqual(self.call(test, 1, 2), (1, 2, 3, {}))
+        self.assertEqual(self.call(test, 1, 2, foo='bar', spam='ham'),
+                         (1, 2, 3, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, b=2, a=1, foo='bar', spam='ham'),
+                         (1, 2, 3, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, a=1, b=2, foo='bar', spam='ham'),
+                         (1, 2, 3, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, 1, b=2, foo='bar', spam='ham'),
+                         (1, 2, 3, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, 1, b=2, c=4, foo='bar', spam='ham'),
+                         (1, 2, 4, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, 1, 2, 4, foo='bar'),
+                         (1, 2, 4, {'foo': 'bar'}))
+        self.assertEqual(self.call(test, c=5, a=4, b=3),
+                         (4, 3, 5, {}))
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_bind_kwonly(self):
+    def test(*, foo):
+        return foo
+    with self.assertRaisesRegex(TypeError,
+                                 'too many positional arguments'):
+        self.call(test, 1)
+    self.assertEqual(self.call(test, foo=1), 1)
+
+    def test(a, *, foo=1, bar):
+        return foo
+    with self.assertRaisesRegex(TypeError,
+                                 "'bar' parameter lacking default value"):
+        self.call(test, 1)
+
+    def test(foo, *, bar):
+        return foo, bar
+    self.assertEqual(self.call(test, 1, bar=2), (1, 2))
+    self.assertEqual(self.call(test, bar=2, foo=1), (1, 2))
+
+    with self.assertRaisesRegex(TypeError,
+                                 'too many keyword arguments'):
+        self.call(test, bar=2, foo=1, spam=10)
+
+    with self.assertRaisesRegex(TypeError,
+                                 'too many positional arguments'):
+        self.call(test, 1, 2)
+
+    with self.assertRaisesRegex(TypeError,
+                                 'too many positional arguments'):
+        self.call(test, 1, 2, bar=2)
+
+    with self.assertRaisesRegex(TypeError,
+                                 'too many keyword arguments'):
+        self.call(test, 1, bar=2, spam='ham')
+
+    with self.assertRaisesRegex(TypeError,
+                                 "'bar' parameter lacking default value"):
+        self.call(test, 1)
+
+    def test(foo, *, bar, **bin):
+        return foo, bar, bin
+    self.assertEqual(self.call(test, 1, bar=2), (1, 2, {}))
+    self.assertEqual(self.call(test, foo=1, bar=2), (1, 2, {}))
+    self.assertEqual(self.call(test, 1, bar=2, spam='ham'),
+                     (1, 2, {'spam': 'ham'}))
+    self.assertEqual(self.call(test, spam='ham', foo=1, bar=2),
+                     (1, 2, {'spam': 'ham'}))
+    with self.assertRaisesRegex(TypeError,
+                                 "'foo' parameter lacking default value"):
+        self.call(test, spam='ham', bar=2)
+    self.assertEqual(self.call(test, 1, bar=2, bin=1, spam=10),
+                     (1, 2, {'bin': 1, 'spam': 10}))
+""")
+#
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_bind_arguments(self):
+    def test(a, *args, b, z=100, **kwargs):
+        pass
+    sig = inspect.signature(test)
+    ba = sig.bind(10, 20, b=30, c=40, args=50, kwargs=60)
+    # we won't have 'z' argument in the bound arguments object, as we didn't
+    # pass it to the 'bind'
+    self.assertEqual(tuple(ba.arguments.items()),
+                     (('a', 10), ('args', (20,)), ('b', 30),
+                      ('kwargs', {'c': 40, 'args': 50, 'kwargs': 60})))
+    self.assertEqual(ba.kwargs,
+                     {'b': 30, 'c': 40, 'args': 50, 'kwargs': 60})
+    self.assertEqual(ba.args, (10, 20))
+""")
+#
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_bind_positional_only(self):
+    P = inspect.Parameter
+
+    def test(a_po, b_po, c_po=3, foo=42, *, bar=50, **kwargs):
+        return a_po, b_po, c_po, foo, bar, kwargs
+
+    sig = inspect.signature(test)
+    new_params = collections.OrderedDict(tuple(sig.parameters.items()))
+    for name in ('a_po', 'b_po', 'c_po'):
+        new_params[name] = new_params[name].replace(kind=P.POSITIONAL_ONLY)
+    new_sig = sig.replace(parameters=new_params.values())
+    test.__signature__ = new_sig
+
+    self.assertEqual(self.call(test, 1, 2, 4, 5, bar=6),
+                     (1, 2, 4, 5, 6, {}))
+
+    with self.assertRaisesRegex(TypeError, "parameter is positional only"):
+        self.call(test, 1, 2, c_po=4)
+
+    with self.assertRaisesRegex(TypeError, "parameter is positional only"):
+        self.call(test, a_po=1, b_po=2)
+""")
+
+
+class TestBoundArguments(unittest.TestCase):
+
+    def __init__(self, *args, **kwargs):
+        unittest.TestCase.__init__(self, *args, **kwargs)
+        if not hasattr(self, 'assertRaisesRegex'):
+            self.assertRaisesRegex = self.assertRaisesRegexp
+
+    def test_signature_bound_arguments_unhashable(self):
+        def foo(a): pass
+        ba = inspect.signature(foo).bind(1)
+
+        with self.assertRaisesRegex(TypeError, 'unhashable type'):
+            hash(ba)
+
+    def test_signature_bound_arguments_equality(self):
+        def foo(a): pass
+        ba = inspect.signature(foo).bind(1)
+        self.assertEqual(ba, ba)
+
+        ba2 = inspect.signature(foo).bind(1)
+        self.assertEqual(ba, ba2)
+
+        ba3 = inspect.signature(foo).bind(2)
+        self.assertNotEqual(ba, ba3)
+        ba3.arguments['a'] = 1
+        self.assertEqual(ba, ba3)
+
+        def bar(b): pass
+        ba4 = inspect.signature(bar).bind(1)
+        self.assertNotEqual(ba, ba4)
+
+
+if __name__ == "__main__":
+    unittest.begin()