summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS.txt11
-rwxr-xr-xcontrib/get-pip.py3
-rw-r--r--contrib/packager/__init__.py3
-rw-r--r--contrib/packager/template.py4
-rw-r--r--docs/conf.py2
-rw-r--r--docs/configuration.txt24
-rw-r--r--docs/contributing.txt (renamed from docs/how-to-contribute.txt)61
-rw-r--r--docs/glossary.txt12
-rw-r--r--docs/index.txt352
-rw-r--r--docs/installing.txt6
-rw-r--r--docs/news.txt74
-rw-r--r--docs/other-tools.txt131
-rw-r--r--docs/requirements.txt (renamed from docs/requirement-format.txt)106
-rw-r--r--docs/running-tests.txt61
-rw-r--r--docs/usage.txt160
-rwxr-xr-xpip/__init__.py13
-rw-r--r--pip/backwardcompat.py24
-rw-r--r--pip/basecommand.py94
-rw-r--r--pip/baseparser.py62
-rw-r--r--pip/commands/bundle.py13
-rw-r--r--pip/commands/freeze.py4
-rw-r--r--pip/commands/help.py12
-rw-r--r--pip/commands/install.py66
-rw-r--r--pip/commands/search.py17
-rw-r--r--pip/commands/uninstall.py1
-rw-r--r--pip/download.py23
-rw-r--r--pip/exceptions.py10
-rw-r--r--pip/index.py33
-rw-r--r--pip/locations.py2
-rw-r--r--pip/log.py5
-rw-r--r--pip/req.py91
-rw-r--r--pip/status_codes.py5
-rw-r--r--pip/util.py21
-rw-r--r--pip/vcs/__init__.py38
-rw-r--r--pip/vcs/bazaar.py8
-rw-r--r--pip/vcs/git.py1
-rw-r--r--pip/vcs/subversion.py96
-rw-r--r--pip/venv.py53
-rw-r--r--setup.py4
-rw-r--r--tests/packages/BrokenEmitsUTF8/broken.py0
-rw-r--r--tests/packages/BrokenEmitsUTF8/setup.py25
-rw-r--r--tests/packages/pkgwithmpkg-1.0-py2.7-macosx10.7.mpkg.zip0
-rw-r--r--tests/packages/pkgwithmpkg-1.0.tar.gz0
-rw-r--r--tests/path.py3
-rw-r--r--tests/test_basic.py82
-rw-r--r--tests/test_cleanup.py9
-rw-r--r--tests/test_config.py35
-rw-r--r--tests/test_download.py15
-rw-r--r--tests/test_extras.py27
-rw-r--r--tests/test_finder.py18
-rw-r--r--tests/test_freeze.py64
-rw-r--r--tests/test_help.py66
-rw-r--r--tests/test_index.py23
-rw-r--r--tests/test_pip.py28
-rw-r--r--tests/test_requirements.py11
-rw-r--r--tests/test_search.py61
-rw-r--r--tests/test_unicode.py25
-rw-r--r--tests/test_uninstall.py1
-rw-r--r--tests/test_upgrade.py36
-rw-r--r--tests/test_vcs_backends.py34
-rw-r--r--tests/test_vcs_bazaar.py29
-rw-r--r--tests/test_vcs_subversion.py21
62 files changed, 1543 insertions, 776 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt
index b501975dc..5fa57113d 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -1,6 +1,7 @@
Alex Grönholm
Alex Morega
Alexandre Conrad
+Antti Kaihola
Armin Ronacher
Brian Rosner
Carl Meyer
@@ -24,13 +25,23 @@ Kenneth Belitzky
Kumar McMillan
Luke Macken
Masklinn
+Marc Abramowitz
+Marcus Smith
+Matt Maker
Nowell Strite
Oliver Tonnhofer
+Olivier Girardot
Patrick Jenkins
Paul Nasrat
+Paul Oswald
+Paul van der Linden
Peter Waller
+Piet Delport
+Qiangning Hong
+Rene Dudfield
Ronny Pfannschmidt
Simon Cross
+Stavros Korokithakis
Thomas Johansson
Vinay Sajip
Vitaly Babiy
diff --git a/contrib/get-pip.py b/contrib/get-pip.py
index 58d90370c..9fd5b3039 100755
--- a/contrib/get-pip.py
+++ b/contrib/get-pip.py
@@ -1106,6 +1106,7 @@ import zlib
import tempfile
import shutil
+
def unpack(sources):
temp_dir = tempfile.mkdtemp('-scratchdir', 'unpacker-')
for package, content in sources.items():
@@ -1122,7 +1123,7 @@ def unpack(sources):
return temp_dir
if __name__ == "__main__":
- if sys.version_info >= (3,0):
+ if sys.version_info >= (3, 0):
exec("def do_exec(co, loc): exec(co, loc)\n")
import pickle
sources = sources.encode("ascii") # ensure bytes
diff --git a/contrib/packager/__init__.py b/contrib/packager/__init__.py
index 580acd204..96068312a 100644
--- a/contrib/packager/__init__.py
+++ b/contrib/packager/__init__.py
@@ -8,6 +8,7 @@ import base64
import os
import fnmatch
+
def find_toplevel(name):
for syspath in sys.path:
lib = os.path.join(syspath, name)
@@ -18,10 +19,12 @@ def find_toplevel(name):
return mod
raise LookupError(name)
+
def pkgname(toplevel, rootpath, path):
parts = path.split(os.sep)[len(rootpath.split(os.sep)):]
return '.'.join([toplevel] + [os.path.splitext(x)[0] for x in parts])
+
def pkg_to_mapping(name):
toplevel = find_toplevel(name)
if os.path.isfile(toplevel):
diff --git a/contrib/packager/template.py b/contrib/packager/template.py
index c7f6fdc76..406958936 100644
--- a/contrib/packager/template.py
+++ b/contrib/packager/template.py
@@ -10,6 +10,7 @@ import zlib
import tempfile
import shutil
+
def unpack(sources):
temp_dir = tempfile.mkdtemp('-scratchdir', 'unpacker-')
for package, content in sources.items():
@@ -25,8 +26,9 @@ def unpack(sources):
mod.close()
return temp_dir
+
if __name__ == "__main__":
- if sys.version_info >= (3,0):
+ if sys.version_info >= (3, 0):
exec("def do_exec(co, loc): exec(co, loc)\n")
import pickle
sources = sources.encode("ascii") # ensure bytes
diff --git a/docs/conf.py b/docs/conf.py
index 040947f89..7dd29dc87 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -47,7 +47,7 @@ copyright = '2008-2011, The pip developers'
# built documents.
#
# The short X.Y version.
-release = "1.0.2"
+release = "1.1"
version = '.'.join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
diff --git a/docs/configuration.txt b/docs/configuration.txt
index 25a6ad511..35d0a347f 100644
--- a/docs/configuration.txt
+++ b/docs/configuration.txt
@@ -115,3 +115,27 @@ Just leave an empty space between the passsed values, e.g.::
is the same as calling::
pip install --find-links=http://mirror1.example.com --find-links=http://mirror2.example.com
+
+Configuration options
+---------------------
+
+Mirror support
+**************
+
+The `PyPI mirroring infrastructure <http://pypi.python.org/mirrors>`_ as
+described in `PEP 381 <http://www.python.org/dev/peps/pep-0381/>`_ can be
+used by passing the ``--use-mirrors`` option to the install command.
+Alternatively, you can use the other ways to configure pip, e.g.::
+
+ $ export PIP_USE_MIRRORS=true
+
+If enabled, pip will automatically query the DNS entry of the mirror index URL
+to find the list of mirrors to use. In case you want to override this list,
+please use the ``--mirrors`` option of the install command, or add to your pip
+configuration file::
+
+ [install]
+ use-mirrors = true
+ mirrors =
+ http://d.pypi.python.org
+ http://b.pypi.python.org
diff --git a/docs/how-to-contribute.txt b/docs/contributing.txt
index be5beb638..c3d0c1356 100644
--- a/docs/how-to-contribute.txt
+++ b/docs/contributing.txt
@@ -1,6 +1,6 @@
-========================
-How To Contribute to Pip
-========================
+=================
+How to contribute
+=================
All kinds of contributions are welcome - code, tests, documentation,
@@ -75,6 +75,61 @@ you can learn how to set up a Hudson CI server like that in the
+Running the Tests
+=================
+
+Pip uses some system tools - VCS related tools - in its tests, so you need to
+intall them (Linux)::
+
+ sudo apt-get install subversion bzr git-core mercurial
+
+Or downloading and installing `Subversion
+<http://subversion.apache.org/packages.html>`_, `Bazaar
+<http://wiki.bazaar.canonical.com/Download>`_, `Git
+<http://git-scm.com/download>`_ and `Mercurial
+<http://mercurial.selenic.com/downloads/>`_ manually.
+
+
+After all requirements (system and python) are installed,
+just run the following command::
+
+ $ python setup.py test
+
+Running tests directly with Nose
+--------------------------------
+
+If you want to run only a selection of the tests, you'll need to run them
+directly with nose instead. Create a virtualenv, and install required
+packages::
+
+ pip install nose virtualenv scripttest mock
+
+Run nosetests::
+
+ nosetests
+
+Or select just a single test to run::
+
+ cd tests; nosetests test_upgrade.py:test_uninstall_rollback
+
+
+Troubleshooting
+---------------
+
+Locale Warnings
+ There was a problem with locales configuration when running tests in a Hudson
+ CI Server that broke some tests. The problem was not with pip, but with
+ `locales` configuration. Hudson was not setting LANG environment variable
+ correctly, so the solution to fix it was changing default language to
+ en_US.UTF-8.
+ The following has been done in a Ubuntu Server 9.10 machine::
+
+ $ sudo locale-gen en_US en_US.UTF-8
+ $ sudo dpkg-reconfigure locales
+ $ sudo update-locale LANG=en_US.UTF-8
+
+
+
Contributing with Tests
=======================
diff --git a/docs/glossary.txt b/docs/glossary.txt
new file mode 100644
index 000000000..c9decea5d
--- /dev/null
+++ b/docs/glossary.txt
@@ -0,0 +1,12 @@
+========
+Glossary
+========
+
+.. glossary::
+
+ PyPI
+ The `Python Package Index`_, formerly known as the Cheese Shop,
+ is a central catalog of Python packages. By default, when
+ installing packages,`pip` searches for them in PyPI.
+
+ .. _`Python Package Index`: http://pypi.python.org/pypi
diff --git a/docs/index.txt b/docs/index.txt
index 286bd7cdf..3f117c0ce 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -1,110 +1,49 @@
pip
===
-pip is a tool for installing and managing Python packages, such as those
-found in the `Python Package Index <http://pypi.python.org/pypi>`_.
+`pip` is a tool for installing and managing Python packages, such as
+those found in the `Python Package Index`_. It's a replacement for
+easy_install_.
+::
-pip is a replacement for `easy_install
-<http://peak.telecommunity.com/DevCenter/EasyInstall>`_. It mostly
-uses the same techniques for finding packages, so packages that are
-easy_installable should be pip-installable as well. This means that
-you can use ``pip install SomePackage`` instead of ``easy_install
-SomePackage``.
+ $ pip install simplejson
+ [... progress report ...]
+ Successfully installed simplejson
-In order to use pip, you must first install `setuptools
-<http://pypi.python.org/pypi/setuptools>`_ or `distribute
-<http://pypi.python.org/pypi/distribute>`_. If you use `virtualenv
-<http://www.virtualenv.org>`_, a copy of pip will be automatically be
-installed in each virtual environment you create.
+.. _`Python Package Index`: http://pypi.python.org/pypi
+.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
+
+Upgrading a package::
+
+ $ pip install --upgrade simplejson
+ [... progress report ...]
+ Successfully installed simplejson
+
+Removing a package::
+
+ $ pip uninstall simplejson
+ Uninstalling simplejson:
+ /home/me/env/lib/python2.7/site-packages/simplejson
+ /home/me/env/lib/python2.7/site-packages/simplejson-2.2.1-py2.7.egg-info
+ Proceed (y/n)? y
+ Successfully uninstalled simplejson
.. comment: split here
.. toctree::
- :maxdepth: 1
+ :maxdepth: 2
- news
installing
- requirement-format
+ usage
+ requirements
configuration
- how-to-contribute
- running-tests
+ other-tools
+ contributing
+ news
+ glossary
.. comment: split here
-Usage
------
-
-Once you have pip, you can use it like this::
-
- $ pip install SomePackage
-
-SomePackage is some package you'll find on `PyPI
-<http://pypi.python.org/pypi/>`_. This installs the package and all
-its dependencies.
-
-pip does other stuff too, with packages, but install is the biggest
-one. You can ``pip uninstall`` too.
-
-You can also install from a URL (that points to a tar or zip file),
-install from some version control system (use URLs like
-``hg+http://domain/repo`` -- or prefix ``git+``, ``svn+`` etc). pip
-knows a bunch of stuff about revisions and stuff, so if you need to do
-things like install a very specific revision from a repository pip can
-do that too.
-
-If you've ever used ``python setup.py develop``, you can do something
-like that with ``pip install -e ./`` -- this works with packages that
-use ``distutils`` too (usually this only works with Setuptools
-projects).
-
-You can use ``pip install --upgrade SomePackage`` to upgrade to a
-newer version, or ``pip install SomePackage==1.0.4`` to install a very
-specific version.
-
-Pip Compared To easy_install
-----------------------------
-
-pip is meant to improve on easy_install. Some of the improvements:
-
-* All packages are downloaded before installation. Partially-completed
- installation doesn't occur as a result.
-
-* Care is taken to present useful output on the console.
-
-* The reasons for actions are kept track of. For instance, if a package is
- being installed, pip keeps track of why that package was required.
-
-* Error messages should be useful.
-
-* The code is relatively concise and cohesive, making it easier to use
- programmatically.
-
-* Packages don't have to be installed as egg archives, they can be installed
- flat (while keeping the egg metadata).
-
-* Native support for other version control systems (Git, Mercurial and Bazaar)
-
-* Uninstallation of packages.
-
-* Simple to define fixed sets of requirements and reliably reproduce a
- set of packages.
-
-pip doesn't do everything that easy_install does. Specifically:
-
-* It cannot install from eggs. It only installs from source. (In the
- future it would be good if it could install binaries from Windows ``.exe``
- or ``.msi`` -- binary install on other platforms is not a priority.)
-
-* It doesn't understand Setuptools extras (like ``package[test]``). This should
- be added eventually.
-
-* It is incompatible with some packages that extensively customize distutils
- or setuptools in their ``setup.py`` files.
-
-pip is complementary with `virtualenv
-<http://pypi.python.org/pypi/virtualenv>`__, and it is encouraged that you use
-virtualenv to isolate your installation.
-
Community
---------
@@ -113,232 +52,3 @@ Bugs can be filed in the `pip issue tracker
<https://github.com/pypa/pip/issues/>`_. Discussion happens on the
`virtualenv email group
<http://groups.google.com/group/python-virtualenv?hl=en>`_.
-
-Uninstall
----------
-
-pip is able to uninstall most installed packages with ``pip uninstall
-package-name``.
-
-Known exceptions include pure-distutils packages installed with
-``python setup.py install`` (such packages leave behind no metadata allowing
-determination of what files were installed), and script wrappers installed
-by develop-installs (``python setup.py develop``).
-
-pip also performs an automatic uninstall of an old version of a package
-before upgrading to a newer version, so outdated files (and egg-info data)
-from conflicting versions aren't left hanging around to cause trouble. The
-old version of the package is automatically restored if the new version
-fails to download or install.
-
-.. _`requirements file`:
-
-Requirements Files
-------------------
-
-When installing software, and Python packages in particular, it's common that
-you get a lot of libraries installed. You just did ``easy_install MyPackage``
-and you get a dozen packages. Each of these packages has its own version.
-
-Maybe you ran that installation and it works. Great! Will it keep working?
-Did you have to provide special options to get it to find everything? Did you
-have to install a bunch of other optional pieces? Most of all, will you be able
-to do it again? Requirements files give you a way to create an *environment*:
-a *set* of packages that work together.
-
-If you've ever tried to setup an application on a new system, or with slightly
-updated pieces, and had it fail, pip requirements are for you. If you
-haven't had this problem then you will eventually, so pip requirements are
-for you too -- requirements make explicit, repeatable installation of packages.
-
-So what are requirements files? They are very simple: lists of packages to
-install. Instead of running something like ``pip MyApp`` and getting
-whatever libraries come along, you can create a requirements file something like::
-
- MyApp
- Framework==0.9.4
- Library>=0.2
-
-Then, regardless of what MyApp lists in ``setup.py``, you'll get a
-specific version of Framework (0.9.4) and at least the 0.2 version of
-Library. (You might think you could list these specific versions in
-MyApp's ``setup.py`` -- but if you do that you'll have to edit MyApp
-if you want to try a new version of Framework, or release a new
-version of MyApp if you determine that Library 0.3 doesn't work with
-your application.) You can also add optional libraries and support
-tools that MyApp doesn't strictly require, giving people a set of
-recommended libraries.
-
-You can also include "editable" packages -- packages that are checked out from
-Subversion, Git, Mercurial and Bazaar. These are just like using the ``-e``
-option to pip. They look like::
-
- -e svn+http://myrepo/svn/MyApp#egg=MyApp
-
-You have to start the URL with ``svn+`` (``git+``, ``hg+`` or ``bzr+``), and
-you have to include ``#egg=Package`` so pip knows what to expect at that URL.
-You can also include ``@rev`` in the URL, e.g., ``@275`` to check out
-revision 275.
-
-Requirement files are mostly *flat*. Maybe ``MyApp`` requires
-``Framework``, and ``Framework`` requires ``Library``. I encourage
-you to still list all these in a single requirement file; it is the
-nature of Python programs that there are implicit bindings *directly*
-between MyApp and Library. For instance, Framework might expose one
-of Library's objects, and so if Library is updated it might directly
-break MyApp. If that happens you can update the requirements file to
-force an earlier version of Library, and you can do that without
-having to re-release MyApp at all.
-
-Read the `requirements file format <http://pip.openplans.org/requirement-format.html>`_ to
-learn about other features.
-
-Freezing Requirements
----------------------
-
-So you have a working set of packages, and you want to be able to install them
-elsewhere. `Requirements files`_ let you install exact versions, but it won't
-tell you what all the exact versions are.
-
-To create a new requirements file from a known working environment, use::
-
- $ pip freeze > stable-req.txt
-
-This will write a listing of *all* installed libraries to ``stable-req.txt``
-with exact versions for every library. You may want to edit the file down after
-generating (e.g., to eliminate unnecessary libraries), but it'll give you a
-stable starting point for constructing your requirements file.
-
-You can also give it an existing requirements file, and it will use that as a
-sort of template for the new file. So if you do::
-
- $ pip freeze -r devel-req.txt > stable-req.txt
-
-it will keep the packages listed in ``devel-req.txt`` in order and preserve
-comments.
-
-Bundles
--------
-
-Another way to distribute a set of libraries is a bundle format (specific to
-pip). This format is not stable at this time (there simply hasn't been
-any feedback, nor a great deal of thought). A bundle file contains all the
-source for your package, and you can have pip install them all together.
-Once you have the bundle file further network access won't be necessary. To
-build a bundle file, do::
-
- $ pip bundle MyApp.pybundle MyApp
-
-(Using a `requirements file`_ would be wise.) Then someone else can get the
-file ``MyApp.pybundle`` and run::
-
- $ pip install MyApp.pybundle
-
-This is *not* a binary format. This only packages source. If you have binary
-packages, then the person who installs the files will have to have a compiler,
-any necessary headers installed, etc. Binary packages are hard, this is
-relatively easy.
-
-Using pip with virtualenv
--------------------------
-
-pip is most nutritious when used with `virtualenv
-<http://pypi.python.org/pypi/virtualenv>`__. One of the reasons pip
-doesn't install "multi-version" eggs is that virtualenv removes much of the need
-for it. Because pip is installed by virtualenv, just use
-``path/to/my/environment/bin/pip`` to install things into that
-specific environment.
-
-To tell pip to only run if there is a virtualenv currently activated,
-and to bail if not, use::
-
- export PIP_REQUIRE_VIRTUALENV=true
-
-To tell pip to automatically use the currently active virtualenv::
-
- export PIP_RESPECT_VIRTUALENV=true
-
-Providing an environment with ``-E`` will be ignored.
-
-Using pip with virtualenvwrapper
----------------------------------
-
-If you are using `virtualenvwrapper
-<http://www.doughellmann.com/projects/virtualenvwrapper/>`_, you might
-want pip to automatically create its virtualenvs in your
-``$WORKON_HOME``.
-
-You can tell pip to do so by defining ``PIP_VIRTUALENV_BASE`` in your
-environment and setting it to the same value as that of
-``$WORKON_HOME``.
-
-Do so by adding the line::
-
- export PIP_VIRTUALENV_BASE=$WORKON_HOME
-
-in your .bashrc under the line starting with ``export WORKON_HOME``.
-
-Using pip with buildout
------------------------
-
-If you are using `zc.buildout
-<http://pypi.python.org/pypi/zc.buildout>`_ you should look at
-`gp.recipe.pip <http://pypi.python.org/pypi/gp.recipe.pip>`_ as an
-option to use pip and virtualenv in your buildouts.
-
-Command line completion
------------------------
-
-pip comes with support for command line completion in bash and zsh and
-allows you tab complete commands and options. To enable it you simply
-need copy the required shell script to the your shell startup file
-(e.g. ``.profile`` or ``.zprofile``) by running the special ``completion``
-command, e.g. for bash::
-
- $ pip completion --bash >> ~/.profile
-
-And for zsh::
-
- $ pip completion --zsh >> ~/.zprofile
-
-Alternatively, you can use the result of the ``completion`` command
-directly with the eval function of you shell, e.g. by adding::
-
- eval "`pip completion --bash`"
-
-to your startup file.
-
-Searching for packages
-----------------------
-
-pip can search the `Python Package Index <http://pypi.python.org/pypi>`_ (PyPI)
-for packages using the ``pip search`` command. To search, run::
-
- $ pip search "query"
-
-The query will be used to search the names and summaries of all packages
-indexed.
-
-pip searches http://pypi.python.org/pypi by default but alternative indexes
-can be searched by using the ``--index`` flag.
-
-Mirror support
---------------
-
-The `PyPI mirroring infrastructure <http://pypi.python.org/mirrors>`_ as
-described in `PEP 381 <http://www.python.org/dev/peps/pep-0381/>`_ can be
-used by passing the ``--use-mirrors`` option to the install command.
-Alternatively, you can use the other ways to configure pip, e.g.::
-
- $ export PIP_USE_MIRRORS=true
-
-If enabled, pip will automatically query the DNS entry of the mirror index URL
-to find the list of mirrors to use. In case you want to override this list,
-please use the ``--mirrors`` option of the install command, or add to your pip
-configuration file::
-
- [install]
- use-mirrors = true
- mirrors =
- http://d.pypi.python.org
- http://b.pypi.python.org
diff --git a/docs/installing.txt b/docs/installing.txt
index 7c5d9999b..5f86c6a49 100644
--- a/docs/installing.txt
+++ b/docs/installing.txt
@@ -23,8 +23,7 @@ Prior to installing pip make sure you have either `setuptools
<http://pypi.python.org/pypi/distribute>`_ installed. Please consult your
operating system's package manager or install it manually::
- $ curl -O http://python-distribute.org/distribute_setup.py
- $ python distribute_setup.py
+ $ curl http://python-distribute.org/distribute_setup.py | python
.. warning::
@@ -37,8 +36,7 @@ Using the installer
Download `get-pip.py <https://raw.github.com/pypa/pip/master/contrib/get-pip.py>`_
and execute it, using the Python interpreter of your choice::
- $ curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py
- $ python get-pip.py
+ $ curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
This may have to be run as root.
diff --git a/docs/news.txt b/docs/news.txt
index 9ae479772..3bcdb2a20 100644
--- a/docs/news.txt
+++ b/docs/news.txt
@@ -1,10 +1,72 @@
-News / Changelog
-================
+====
+News
+====
-Next release (1.1) schedule
+Changelog
+=========
+
+Next release (1.2) schedule
---------------------------
-Beta release mid-July 2011, final release early August.
+Beta and final releases planned for the second half of 2012.
+
+1.1 (2012-02-16)
+----------------
+
+* Fixed issue #326 - don't crash when a package's setup.py emits UTF-8 and
+ then fails. Thanks Marc Abramowitz.
+
+* Added ``--target`` option for installing directly to arbitrary directory.
+ Thanks Stavros Korokithakis.
+
+* Added support for authentication with Subversion repositories. Thanks
+ Qiangning Hong.
+
+* Fixed issue #315 - ``--download`` now downloads dependencies as well.
+ Thanks Qiangning Hong.
+
+* Errors from subprocesses will display the current working directory.
+ Thanks Antti Kaihola.
+
+* Fixed issue #369 - compatibility with Subversion 1.7. Thanks Qiangning
+ Hong. Note that setuptools remains incompatible with Subversion 1.7; to
+ get the benefits of pip's support you must use Distribute rather than
+ setuptools.
+
+* Fixed issue #57 - ignore py2app-generated OS X mpkg zip files in finder.
+ Thanks Rene Dudfield.
+
+* Fixed issue #182 - log to ~/Library/Logs/ by default on OS X framework
+ installs. Thanks Dan Callahan for report and patch.
+
+* Fixed issue #310 - understand version tags without minor version ("py3")
+ in sdist filenames. Thanks Stuart Andrews for report and Olivier Girardot for
+ patch.
+
+* Fixed issue #7 - Pip now supports optionally installing setuptools
+ "extras" dependencies; e.g. "pip install Paste[openid]". Thanks Matt Maker
+ and Olivier Girardot.
+
+* Fixed issue #391 - freeze no longer borks on requirements files with
+ --index-url or --find-links. Thanks Herbert Pfennig.
+
+* Fixed issue #288 - handle symlinks properly. Thanks lebedov for the patch.
+
+* Fixed issue #49 - pip install -U no longer reinstalls the same versions of
+ packages. Thanks iguananaut for the pull request.
+
+* Removed ``-E`` option and ``PIP_RESPECT_VIRTUALENV``; both use a
+ restart-in-venv mechanism that's broken, and neither one is useful since
+ every virtualenv now has pip inside it.
+
+* Fixed issue #366 - pip throws IndexError when it calls `scraped_rel_links`
+
+* Fixed issue #22 - pip search should set and return a userful shell status code
+
+* Fixed issue #351 and #365 - added global ``--exists-action`` command line
+ option to easier script file exists conflicts, e.g. from editable
+ requirements from VCS that have a changed repo URL.
+
1.0.2 (2011-07-16)
------------------
@@ -13,8 +75,8 @@ Beta release mid-July 2011, final release early August.
* Fixed issue #295 - Reinstall a package when using the ``install -I`` option
* Fixed issue #283 - Finds a Git tag pointing to same commit as origin/master
* Fixed issue #279 - Use absolute path for path to docs in setup.py
-* Fixed issue #320 - Correctly handle exceptions on Python3.
-* Fixed issue #314 - Correctly parse ``--editable`` lines in requirements files
+* Fixed issue #314 - Correctly handle exceptions on Python3.
+* Fixed issue #320 - Correctly parse ``--editable`` lines in requirements files
1.0.1 (2011-04-30)
------------------
diff --git a/docs/other-tools.txt b/docs/other-tools.txt
new file mode 100644
index 000000000..a705e8ab2
--- /dev/null
+++ b/docs/other-tools.txt
@@ -0,0 +1,131 @@
+=============================
+Relationship with other tools
+=============================
+
+Pip Compared To easy_install
+----------------------------
+
+pip is meant to improve on easy_install. Some of the improvements:
+
+* All packages are downloaded before installation. Partially-completed
+ installation doesn't occur as a result.
+
+* Care is taken to present useful output on the console.
+
+* The reasons for actions are kept track of. For instance, if a package is
+ being installed, pip keeps track of why that package was required.
+
+* Error messages should be useful.
+
+* The code is relatively concise and cohesive, making it easier to use
+ programmatically.
+
+* Packages don't have to be installed as egg archives, they can be installed
+ flat (while keeping the egg metadata).
+
+* Native support for other version control systems (Git, Mercurial and Bazaar)
+
+* Uninstallation of packages.
+
+* Simple to define fixed sets of requirements and reliably reproduce a
+ set of packages.
+
+pip doesn't do everything that easy_install does. Specifically:
+
+* It cannot install from eggs. It only installs from source. (In the
+ future it would be good if it could install binaries from Windows ``.exe``
+ or ``.msi`` -- binary install on other platforms is not a priority.)
+
+* It doesn't understand Setuptools extras (like ``package[test]``). This should
+ be added eventually.
+
+* It is incompatible with some packages that extensively customize distutils
+ or setuptools in their ``setup.py`` files.
+
+pip is complementary with `virtualenv
+<http://pypi.python.org/pypi/virtualenv>`__, and it is encouraged that you use
+virtualenv to isolate your installation.
+
+Using pip with virtualenv
+-------------------------
+
+pip is most nutritious when used with `virtualenv
+<http://pypi.python.org/pypi/virtualenv>`__. One of the reasons pip
+doesn't install "multi-version" eggs is that virtualenv removes much of the need
+for it. Because pip is installed by virtualenv, just use
+``path/to/my/environment/bin/pip`` to install things into that
+specific environment.
+
+To tell pip to only run if there is a virtualenv currently activated,
+and to bail if not, use::
+
+ export PIP_REQUIRE_VIRTUALENV=true
+
+
+Using pip with virtualenvwrapper
+---------------------------------
+
+If you are using `virtualenvwrapper
+<http://www.doughellmann.com/projects/virtualenvwrapper/>`_, you might
+want pip to automatically create its virtualenvs in your
+``$WORKON_HOME``.
+
+You can tell pip to do so by defining ``PIP_VIRTUALENV_BASE`` in your
+environment and setting it to the same value as that of
+``$WORKON_HOME``.
+
+Do so by adding the line::
+
+ export PIP_VIRTUALENV_BASE=$WORKON_HOME
+
+in your .bashrc under the line starting with ``export WORKON_HOME``.
+
+Using pip with buildout
+-----------------------
+
+If you are using `zc.buildout
+<http://pypi.python.org/pypi/zc.buildout>`_ you should look at
+`gp.recipe.pip <http://pypi.python.org/pypi/gp.recipe.pip>`_ as an
+option to use pip and virtualenv in your buildouts.
+
+Using pip with the "user scheme"
+--------------------------------
+
+With Python 2.6 came the `"user scheme" for installation
+<http://docs.python.org/install/index.html#alternate-installation-the-user-scheme>`_, which means that all
+Python distributions support an alternative install location that is specific to a user.
+The default location for each OS is explained in the python documentation
+for the `site.USER_BASE <http://docs.python.org/library/site.html#site.USER_BASE>`_ variable.
+This mode of installation can be turned on by
+specifying the ``--user`` option to ``pip install``.
+
+Moreover, the "user scheme" can be customized by setting the
+``PYTHONUSERBASE`` environment variable, which updates the value of ``site.USER_BASE``.
+
+To install "somepackage" into an environment with site.USER_BASE customized to '/myappenv', do the following::
+
+ export PYTHONUSERBASE=/myappenv
+ pip install --user somepackage
+
+
+Command line completion
+-----------------------
+
+pip comes with support for command line completion in bash and zsh and
+allows you tab complete commands and options. To enable it you simply
+need copy the required shell script to the your shell startup file
+(e.g. ``.profile`` or ``.zprofile``) by running the special ``completion``
+command, e.g. for bash::
+
+ $ pip completion --bash >> ~/.profile
+
+And for zsh::
+
+ $ pip completion --zsh >> ~/.zprofile
+
+Alternatively, you can use the result of the ``completion`` command
+directly with the eval function of you shell, e.g. by adding::
+
+ eval "`pip completion --bash`"
+
+to your startup file.
diff --git a/docs/requirement-format.txt b/docs/requirements.txt
index d326ea63e..326937da6 100644
--- a/docs/requirement-format.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,92 @@
-The requirements file format
-============================
+.. _`requirements-files`:
+
+==================
+Requirements files
+==================
+
+When installing software, and Python packages in particular, it's common that
+you get a lot of libraries installed. You just did ``easy_install MyPackage``
+and you get a dozen packages. Each of these packages has its own version.
+
+Maybe you ran that installation and it works. Great! Will it keep working?
+Did you have to provide special options to get it to find everything? Did you
+have to install a bunch of other optional pieces? Most of all, will you be able
+to do it again? Requirements files give you a way to create an *environment*:
+a *set* of packages that work together.
+
+If you've ever tried to setup an application on a new system, or with slightly
+updated pieces, and had it fail, pip requirements are for you. If you
+haven't had this problem then you will eventually, so pip requirements are
+for you too -- requirements make explicit, repeatable installation of packages.
+
+So what are requirements files? They are very simple: lists of packages to
+install. Instead of running something like ``pip install MyApp`` and
+getting whatever libraries come along, you can create a requirements file
+something like::
+
+ MyApp
+ Framework==0.9.4
+ Library>=0.2
+
+If you save this in ``requirements.txt``, then you can ``pip install -r
+requirements.txt``. Regardless of what MyApp lists in ``setup.py``, you'll
+get a specific version of Framework (0.9.4) and at least the 0.2 version of
+Library. (You might think you could list these specific versions in MyApp's
+``setup.py`` -- but if you do that you'll have to edit MyApp if you want to
+try a new version of Framework, or release a new version of MyApp if you
+determine that Library 0.3 doesn't work with your application.) You can also
+add optional libraries and support tools that MyApp doesn't strictly
+require, giving people a set of recommended libraries.
+
+You can also include "editable" packages -- packages that are checked out from
+Subversion, Git, Mercurial and Bazaar. These are just like using the ``-e``
+option to pip. They look like::
+
+ -e svn+http://myrepo/svn/MyApp#egg=MyApp
+
+You have to start the URL with ``svn+`` (``git+``, ``hg+`` or ``bzr+``), and
+you have to include ``#egg=Package`` so pip knows what to expect at that URL.
+You can also include ``@rev`` in the URL, e.g., ``@275`` to check out
+revision 275.
+
+Requirement files are mostly *flat*. Maybe ``MyApp`` requires
+``Framework``, and ``Framework`` requires ``Library``. I encourage
+you to still list all these in a single requirement file; it is the
+nature of Python programs that there are implicit bindings *directly*
+between MyApp and Library. For instance, Framework might expose one
+of Library's objects, and so if Library is updated it might directly
+break MyApp. If that happens you can update the requirements file to
+force an earlier version of Library, and you can do that without
+having to re-release MyApp at all.
+
+Read the `requirements file format`_ to learn about other features.
+
+Freezing Requirements
+=====================
+
+So you have a working set of packages, and you want to be able to install them
+elsewhere. `Requirements files`_ let you install exact versions, but it won't
+tell you what all the exact versions are.
+
+To create a new requirements file from a known working environment, use::
+
+ $ pip freeze > stable-req.txt
+
+This will write a listing of *all* installed libraries to ``stable-req.txt``
+with exact versions for every library. You may want to edit the file down after
+generating (e.g., to eliminate unnecessary libraries), but it'll give you a
+stable starting point for constructing your requirements file.
+
+You can also give it an existing requirements file, and it will use that as a
+sort of template for the new file. So if you do::
+
+ $ pip freeze -r devel-req.txt > stable-req.txt
+
+it will keep the packages listed in ``devel-req.txt`` in order and preserve
+comments.
+
+The _`requirements file format`
+===============================
The requirements file is a way to get pip to install specific packages
to make up an *environment*. This document describes that format. To
@@ -11,9 +98,15 @@ installed. For example::
MyPackage==3.0
-tells pip to install the 3.0 version of MyPackage.
+tells pip to install the 3.0 version of MyPackage.
+
+You can also request `extras`_ in the requirements file::
+
+ MyPackage==3.0 [PDF]
+
+.. _extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies
-You can also install a package in an "editable" form. This puts the
+Packages may also be installed in an "editable" form. This puts the
source code into ``src/distname`` (making the name lower case) and
runs ``python setup.py develop`` on the package. To indicate
editable, use ``-e``, like::
@@ -76,7 +169,7 @@ Pip currently supports cloning over ``git``, ``git+http`` and ``git+ssh``::
-e git+http://git.myproject.org/MyProject/#egg=MyProject
-e git+ssh://git@myproject.org/MyProject/#egg=MyProject
-Passing branch names, a commit hash or a tag name is also possible::
+Passing branch names, a commit hash or a tag name is also possible::
-e git://git.myproject.org/MyProject.git@master#egg=MyProject
-e git://git.myproject.org/MyProject.git@v1.0#egg=MyProject
@@ -104,12 +197,13 @@ Bazaar
~~~~~~
Pip supports Bazaar using the ``bzr+http``, ``bzr+https``, ``bzr+ssh``,
-``bzr+sftp`` and ``bzr+ftp`` schemes::
+``bzr+sftp``, ``bzr+ftp`` and ``bzr+lp`` schemes::
-e bzr+http://bzr.myproject.org/MyProject/trunk/#egg=MyProject
-e bzr+sftp://user@myproject.org/MyProject/trunk/#egg=MyProject
-e bzr+ssh://user@myproject.org/MyProject/trunk/#egg=MyProject
-e bzr+ftp://user@myproject.org/MyProject/trunk/#egg=MyProject
+ -e bzr+lp:MyProject#egg=MyProject
Tags or revisions can be installed like this::
diff --git a/docs/running-tests.txt b/docs/running-tests.txt
deleted file mode 100644
index bfed34b3b..000000000
--- a/docs/running-tests.txt
+++ /dev/null
@@ -1,61 +0,0 @@
-=================
-Running the Tests
-=================
-
-System Requirements
-===================
-
-Pip uses some system tools - VCS related tools - in its tests, so you need to
-intall them (Linux)::
-
- sudo apt-get install subversion bzr git-core mercurial
-
-Or downloading and installing `Subversion
-<http://subversion.apache.org/packages.html>`_, `Bazaar
-<http://wiki.bazaar.canonical.com/Download>`_, `Git
-<http://git-scm.com/download>`_ and `Mercurial
-<http://mercurial.selenic.com/downloads/>`_ manually.
-
-
-How To Run Tests
-================
-
-After all requirements (system and python) are installed,
-just run the following command::
-
- $ python setup.py test
-
-Running tests directly with Nose
---------------------------------
-
-If you want to run only a selection of the tests, you'll need to run them
-directly with nose instead. Create a virtualenv, and install required
-packages::
-
- pip install nose virtualenv scripttest mock
-
-Run nosetests::
-
- nosetests
-
-Or select just a single test to run::
-
- cd tests; nosetests test_upgrade.py:test_uninstall_rollback
-
-
-Troubleshooting
-===============
-
-Locale Warnings
----------------
-
-There was a problem with locales configuration when running tests in a Hudson
-CI Server that broke some tests. The problem was not with pip, but with
-`locales` configuration. Hudson was not setting LANG environment variable
-correctly, so the solution to fix it was changing default language to
-en_US.UTF-8.
-The following has been done in a Ubuntu Server 9.10 machine::
-
- $ sudo locale-gen en_US en_US.UTF-8
- $ sudo dpkg-reconfigure locales
- $ sudo update-locale LANG=en_US.UTF-8
diff --git a/docs/usage.txt b/docs/usage.txt
new file mode 100644
index 000000000..991d0d54e
--- /dev/null
+++ b/docs/usage.txt
@@ -0,0 +1,160 @@
+=====
+Usage
+=====
+
+Install packages
+----------------
+
+The simplest way to install a package is by specifying its name::
+
+ $ pip install SomePackage
+
+`SomePackage` is downloaded from :term:`PyPI`, along with its
+dependencies, and installed.
+
+If `SomePackage` is already installed, and you need a newer version, use ``pip
+install --upgrade SomePackage``. You can also request a specific version (``pip
+install SomePackage==1.0.4``) and specify `setuptools extras`_ (``pip install
+SomePackage[PDF]``).
+
+You can also install from a particular source distribution file, either
+local or remote::
+
+ $ pip install ./downloads/SomePackage-1.0.4.tar.gz
+ $ pip install http://my.package.repo/SomePackage-1.0.4.zip
+
+.. _setuptools extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies
+
+
+Edit mode
+*********
+
+Packages normally_ install under ``site-packages``, but when you're
+making changes, it makes more sense to run the package straight from the
+checked-out source tree. "Editable" installs create a ``.pth`` file in
+``site-packages`` that extends Python's import path to find the
+package::
+
+ $ pip install -e path/to/SomePackage
+
+.. _normally: http://docs.python.org/install/index.html#how-installation-works
+
+
+Version control systems
+***********************
+
+Pip knows how to check out a package from version control. `Subversion`,
+`Git`, `Mercurial` and `Bazaar` are supported. The repository will be
+checked out in a temporary folder, installed, and cleaned up::
+
+ $ pip install git+https://github.com/simplejson/simplejson.git
+ $ pip install svn+svn://svn.zope.org/repos/main/zope.interface/trunk/
+
+This can be combined with the `-e` flag, and Pip will perform the
+checkout in ``./src/``. You need to supply a name for the checkout
+folder by appending a hash to the repository URL::
+
+ $ pip install -e git+https://github.com/lakshmivyas/hyde.git#egg=hyde
+
+Note that only basic checking-out of a repo is supported; pip will not
+handle advanced VCS-specific features such as submodules or subrepos.
+
+
+Alternate package repositories
+******************************
+
+pip searches in :term:`PyPI` by default, but this can be overridden using the
+``--index-url`` option::
+
+ $ pip install --index-url http://d.pypi.python.org/simple/ SomePackage
+
+If you have your own package index with a few additional packages, you may want
+to to specify additional index URLs while still also using :term:`PyPI`::
+
+ $ pip install --extra-index-url http://my.package.repo/ SomePackage
+
+A "package index" used with ``--index-url`` or ``--extra-index-url`` can be as
+simple as a static-web-served directory, with automatic indexes on, with a
+subdirectory per package and sdists (tarballs created with ``python setup.py
+sdist``) in that directory::
+
+ mypackage/
+ mypackage-0.7.8.tar.gz
+ mypackage-1.0.1.tar.gz
+ otherpackage/
+ otherpackage-2.3.5.tar.gz
+
+If the number of packages in the index is small, it's even simpler to skip the
+subdirectories: put all of the sdists in a single directory and use pip's
+``--find-links`` option with a URL to that directory::
+
+ mypackage-0.7.8.tar.gz
+ mypackage-1.0.1.tar.gz
+ otherpackage-2.3.5.tar.gz
+
+``--find-links`` also supports local paths, so installation need not require a
+network connection.
+
+Like ``--extra-index-url``, ``--find-links`` is additive by default, it does
+not replace or supersede the index. All package sources are checked, and the
+latest qualifying version for every requested package is used. If you want only
+your ``-find-links`` URL used as package source, you need to pair it with
+``--no-index``.
+
+``--index-url``, ``--extra-index-url`` and ``--find-links`` can all be used
+within a :ref:`requirements file <requirements-files>` in addition to on the
+command line directly.
+
+
+Uninstall packages
+------------------
+
+pip is able to uninstall most installed packages with ``pip uninstall
+package-name``.
+
+Known exceptions include pure-distutils packages installed with
+``python setup.py install`` (such packages leave behind no metadata allowing
+determination of what files were installed), and script wrappers installed
+by develop-installs (``python setup.py develop``).
+
+pip also performs an automatic uninstall of an old version of a package
+before upgrading to a newer version, so outdated files (and egg-info data)
+from conflicting versions aren't left hanging around to cause trouble. The
+old version of the package is automatically restored if the new version
+fails to download or install.
+
+
+Searching for packages
+----------------------
+
+pip can search :term:`PyPI` for packages using the ``pip search``
+command::
+
+ $ pip search "query"
+
+The query will be used to search the names and summaries of all
+packages. With the ``--index`` option you can search in a different
+repository.
+
+
+Bundles
+-------
+
+Another way to distribute a set of libraries is a bundle format (specific to
+pip). This format is not stable at this time (there simply hasn't been
+any feedback, nor a great deal of thought). A bundle file contains all the
+source for your package, and you can have pip install them all together.
+Once you have the bundle file further network access won't be necessary. To
+build a bundle file, do::
+
+ $ pip bundle MyApp.pybundle MyApp
+
+(Using a :ref:`requirements file <requirements-files>` would be wise.) Then
+someone else can get the file ``MyApp.pybundle`` and run::
+
+ $ pip install MyApp.pybundle
+
+This is *not* a binary format. This only packages source. If you have binary
+packages, then the person who installs the files will have to have a compiler,
+any necessary headers installed, etc. Binary packages are hard, this is
+relatively easy.
diff --git a/pip/__init__.py b/pip/__init__.py
index 6622f9c22..9580790a2 100755
--- a/pip/__init__.py
+++ b/pip/__init__.py
@@ -7,7 +7,7 @@ import sys
import re
import difflib
-from pip.backwardcompat import u, walk_packages, console_to_str
+from pip.backwardcompat import walk_packages, console_to_str
from pip.basecommand import command_dict, load_command, load_all_commands, command_names
from pip.baseparser import parser
from pip.exceptions import InstallationError
@@ -113,7 +113,8 @@ def main(initial_args=None):
parser.error('No command by the name %(script)s %(arg)s\n '
'(maybe you meant "%(script)s %(guess)s")' % error_dict)
command = command_dict[command]
- return command.main(initial_args, args[1:], options)
+ return command.main(args[1:], options)
+
def bootstrap():
"""
@@ -251,12 +252,12 @@ def call_subprocess(cmd, show_stdout=True,
logger.notify('Complete output from command %s:' % command_desc)
logger.notify('\n'.join(all_output) + '\n----------------------------------------')
raise InstallationError(
- "Command %s failed with error code %s"
- % (command_desc, proc.returncode))
+ "Command %s failed with error code %s in %s"
+ % (command_desc, proc.returncode, cwd))
else:
logger.warn(
- "Command %s had error code %s"
- % (command_desc, proc.returncode))
+ "Command %s had error code %s in %s"
+ % (command_desc, proc.returncode, cwd))
if stdout is not None:
return ''.join(all_output)
diff --git a/pip/backwardcompat.py b/pip/backwardcompat.py
index 4e14d1a4f..e33da9896 100644
--- a/pip/backwardcompat.py
+++ b/pip/backwardcompat.py
@@ -37,7 +37,7 @@ except NameError:
console_encoding = sys.__stdout__.encoding
if sys.version_info >= (3,):
- from io import StringIO
+ from io import StringIO, BytesIO
from functools import reduce
from urllib.error import URLError, HTTPError
from queue import Queue, Empty
@@ -50,14 +50,25 @@ if sys.version_info >= (3,):
import xmlrpc.client as xmlrpclib
import urllib.parse as urlparse
import http.client as httplib
+
def cmp(a, b):
return (a > b) - (a < b)
+
def b(s):
return s.encode('utf-8')
+
def u(s):
return s.decode('utf-8')
+
def console_to_str(s):
- return s.decode(console_encoding)
+ try:
+ return s.decode(console_encoding)
+ except UnicodeDecodeError:
+ return s.decode('utf_8')
+
+ def fwrite(f, s):
+ f.buffer.write(b(s))
+
bytes = bytes
string_types = (str,)
raw_input = input
@@ -73,17 +84,25 @@ else:
import ConfigParser
import xmlrpclib
import httplib
+
def b(s):
return s
+
def u(s):
return s
+
def console_to_str(s):
return s
+
+ def fwrite(f, s):
+ f.write(s)
+
bytes = str
string_types = (basestring,)
reduce = reduce
cmp = cmp
raw_input = raw_input
+ BytesIO = StringIO
try:
from email.parser import FeedParser
@@ -93,6 +112,7 @@ except ImportError:
from distutils.sysconfig import get_python_lib, get_python_version
+
def copytree(src, dst):
if sys.version_info < (2, 5):
before_last_dir = os.path.dirname(dst)
diff --git a/pip/basecommand.py b/pip/basecommand.py
index 153ee52be..12bcd6211 100644
--- a/pip/basecommand.py
+++ b/pip/basecommand.py
@@ -10,9 +10,11 @@ from pip import commands
from pip.log import logger
from pip.baseparser import parser, ConfigOptionParser, UpdatingDefaultsHelpFormatter
from pip.download import urlopen
-from pip.exceptions import BadCommand, InstallationError, UninstallationError
-from pip.venv import restart_in_venv
-from pip.backwardcompat import StringIO, urllib, urllib2, walk_packages
+from pip.exceptions import (BadCommand, InstallationError, UninstallationError,
+ CommandError)
+from pip.backwardcompat import StringIO, walk_packages
+from pip.status_codes import SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND
+
__all__ = ['command_dict', 'Command', 'load_all_commands',
'load_command', 'command_names']
@@ -45,10 +47,11 @@ class Command(object):
def merge_options(self, initial_options, options):
# Make sure we have all global options carried over
- for attr in ['log', 'venv', 'proxy', 'venv_base', 'require_venv',
- 'respect_venv', 'log_explicit_levels', 'log_file',
- 'timeout', 'default_vcs', 'skip_requirements_regex',
- 'no_input']:
+ for attr in ['log', 'proxy', 'require_venv',
+ 'log_explicit_levels', 'log_file',
+ 'timeout', 'default_vcs',
+ 'skip_requirements_regex',
+ 'no_input', 'exists_action']:
setattr(options, attr, getattr(initial_options, attr) or getattr(options, attr))
options.quiet += initial_options.quiet
options.verbose += initial_options.verbose
@@ -56,7 +59,7 @@ class Command(object):
def setup_logging(self):
pass
- def main(self, complete_args, args, initial_options):
+ def main(self, args, initial_options):
options, args = self.parser.parse_args(args)
self.merge_options(initial_options, options)
@@ -73,44 +76,18 @@ class Command(object):
self.setup_logging()
- if options.require_venv and not options.venv:
+ if options.no_input:
+ os.environ['PIP_NO_INPUT'] = '1'
+
+ if options.exists_action:
+ os.environ['PIP_EXISTS_ACTION'] = ''.join(options.exists_action)
+
+ if options.require_venv:
# If a venv is required check if it can really be found
if not os.environ.get('VIRTUAL_ENV'):
logger.fatal('Could not find an activated virtualenv (required).')
- sys.exit(3)
- # Automatically install in currently activated venv if required
- options.respect_venv = True
-
- if args and args[-1] == '___VENV_RESTART___':
- ## FIXME: We don't do anything this this value yet:
- args = args[:-2]
- options.venv = None
- else:
- # If given the option to respect the activated environment
- # check if no venv is given as a command line parameter
- if options.respect_venv and os.environ.get('VIRTUAL_ENV'):
- if options.venv and os.path.exists(options.venv):
- # Make sure command line venv and environmental are the same
- if (os.path.realpath(os.path.expanduser(options.venv)) !=
- os.path.realpath(os.environ.get('VIRTUAL_ENV'))):
- logger.fatal("Given virtualenv (%s) doesn't match "
- "currently activated virtualenv (%s)."
- % (options.venv, os.environ.get('VIRTUAL_ENV')))
- sys.exit(3)
- else:
- options.venv = os.environ.get('VIRTUAL_ENV')
- logger.info('Using already activated environment %s' % options.venv)
- if options.venv:
- logger.info('Running in environment %s' % options.venv)
- site_packages=False
- if options.site_packages:
- site_packages=True
- restart_in_venv(options.venv, options.venv_base, site_packages,
- complete_args)
- # restart_in_venv should actually never return, but for clarity...
- return
-
- ## FIXME: not sure if this sure come before or after venv restart
+ sys.exit(VIRTUALENV_NOT_FOUND)
+
if options.log:
log_fp = open_logfile(options.log, 'a')
logger.consumers.append((logger.DEBUG, log_fp))
@@ -121,30 +98,43 @@ class Command(object):
urlopen.setup(proxystr=options.proxy, prompting=not options.no_input)
- exit = 0
+ exit = SUCCESS
+ store_log = False
try:
- self.run(options, args)
+ status = self.run(options, args)
+ # FIXME: all commands should return an exit status
+ # and when it is done, isinstance is not needed anymore
+ if isinstance(status, int):
+ exit = status
except (InstallationError, UninstallationError):
e = sys.exc_info()[1]
logger.fatal(str(e))
logger.info('Exception information:\n%s' % format_exc())
- exit = 1
+ store_log = True
+ exit = ERROR
except BadCommand:
e = sys.exc_info()[1]
logger.fatal(str(e))
logger.info('Exception information:\n%s' % format_exc())
- exit = 1
+ store_log = True
+ exit = ERROR
+ except CommandError:
+ e = sys.exc_info()[1]
+ logger.fatal('ERROR: %s' % e)
+ logger.info('Exception information:\n%s' % format_exc())
+ exit = ERROR
except KeyboardInterrupt:
logger.fatal('Operation cancelled by user')
logger.info('Exception information:\n%s' % format_exc())
- exit = 1
+ store_log = True
+ exit = ERROR
except:
logger.fatal('Exception:\n%s' % format_exc())
- exit = 2
-
+ store_log = True
+ exit = UNKNOWN_ERROR
if log_fp is not None:
log_fp.close()
- if exit:
+ if store_log:
log_fn = options.log_file
text = '\n'.join(complete_log)
logger.fatal('Storing complete log in %s' % log_fn)
@@ -154,8 +144,6 @@ class Command(object):
return exit
-
-
def format_exc(exc_info=None):
if exc_info is None:
exc_info = sys.exc_info()
diff --git a/pip/baseparser.py b/pip/baseparser.py
index 5e4898530..b3864f3da 100644
--- a/pip/baseparser.py
+++ b/pip/baseparser.py
@@ -46,14 +46,11 @@ class ConfigOptionParser(optparse.OptionParser):
config = {}
# 1. config files
for section in ('global', self.name):
- config.update(dict(self.get_config_section(section)))
+ config.update(self.normalize_keys(self.get_config_section(section)))
# 2. environmental variables
- config.update(dict(self.get_environ_vars()))
+ config.update(self.normalize_keys(self.get_environ_vars()))
# Then set the options with those values
for key, val in config.items():
- key = key.replace('_', '-')
- if not key.startswith('--'):
- key = '--%s' % key # only prefer long opts
option = self.get_option(key)
if option is not None:
# ignore empty values
@@ -75,6 +72,18 @@ class ConfigOptionParser(optparse.OptionParser):
defaults[option.dest] = val
return defaults
+ def normalize_keys(self, items):
+ """Return a config dictionary with normalized keys regardless of
+ whether the keys were specified in environment variables or in config
+ files"""
+ normalized = {}
+ for key, val in items:
+ key = key.replace('_', '-')
+ if not key.startswith('--'):
+ key = '--%s' % key # only prefer long opts
+ normalized[key] = val
+ return normalized
+
def get_config_section(self, name):
"""Get a section of a configuration"""
if self.config.has_section(name):
@@ -123,41 +132,12 @@ parser.add_option(
action='store_true',
help='Show help')
parser.add_option(
- '-E', '--environment',
- dest='venv',
- metavar='DIR',
- help='virtualenv environment to run pip in (either give the '
- 'interpreter or the environment base directory)')
-parser.add_option(
- '-s', '--enable-site-packages',
- dest='site_packages',
- action='store_true',
- help='Include site-packages in virtualenv if one is to be '
- 'created. Ignored if --environment is not used or '
- 'the virtualenv already exists.')
-parser.add_option(
- # Defines a default root directory for virtualenvs, relative
- # virtualenvs names/paths are considered relative to it.
- '--virtualenv-base',
- dest='venv_base',
- type='str',
- default='',
- help=optparse.SUPPRESS_HELP)
-parser.add_option(
# Run only if inside a virtualenv, bail if not.
'--require-virtualenv', '--require-venv',
dest='require_venv',
action='store_true',
default=False,
help=optparse.SUPPRESS_HELP)
-parser.add_option(
- # Use automatically an activated virtualenv instead of installing
- # globally. -E will be ignored if used.
- '--respect-virtualenv', '--respect-venv',
- dest='respect_venv',
- action='store_true',
- default=False,
- help=optparse.SUPPRESS_HELP)
parser.add_option(
'-v', '--verbose',
@@ -229,4 +209,18 @@ parser.add_option(
default='',
help=optparse.SUPPRESS_HELP)
+parser.add_option(
+ # Option when path already exist
+ '--exists-action',
+ dest='exists_action',
+ type='choice',
+ choices=['s', 'i', 'w', 'b'],
+ default=[],
+ action='append',
+ help="Default action when a path already exists."
+ "Use this option more then one time to specify "
+ "another action if a certain option is not "
+ "available, choices: "
+ "(s)witch, (i)gnore, (w)ipe, (b)ackup")
+
parser.disable_interspersed_args()
diff --git a/pip/commands/bundle.py b/pip/commands/bundle.py
index fb0f75704..f782f1bc3 100644
--- a/pip/commands/bundle.py
+++ b/pip/commands/bundle.py
@@ -13,14 +13,19 @@ class BundleCommand(InstallCommand):
def __init__(self):
super(BundleCommand, self).__init__()
+ # bundle uses different default source and build dirs
+ build_opt = self.parser.get_option("--build")
+ build_opt.default = backup_dir(build_prefix, '-bundle')
+ src_opt = self.parser.get_option("--src")
+ src_opt.default = backup_dir(src_prefix, '-bundle')
+ self.parser.set_defaults(**{
+ src_opt.dest: src_opt.default,
+ build_opt.dest: build_opt.default,
+ })
def run(self, options, args):
if not args:
raise InstallationError('You must give a bundle filename')
- if not options.build_dir:
- options.build_dir = backup_dir(build_prefix, '-bundle')
- if not options.src_dir:
- options.src_dir = backup_dir(src_prefix, '-bundle')
# We have to get everything when creating a bundle:
options.ignore_installed = True
logger.notify('Putting temporary build files in %s and source/develop files in %s'
diff --git a/pip/commands/freeze.py b/pip/commands/freeze.py
index 01b5df934..03ac80f55 100644
--- a/pip/commands/freeze.py
+++ b/pip/commands/freeze.py
@@ -85,7 +85,9 @@ class FreezeCommand(Command):
elif (line.startswith('-r') or line.startswith('--requirement')
or line.startswith('-Z') or line.startswith('--always-unzip')
or line.startswith('-f') or line.startswith('-i')
- or line.startswith('--extra-index-url')):
+ or line.startswith('--extra-index-url')
+ or line.startswith('--find-links')
+ or line.startswith('--index-url')):
f.write(line)
continue
else:
diff --git a/pip/commands/help.py b/pip/commands/help.py
index b5b31e702..4d504c521 100644
--- a/pip/commands/help.py
+++ b/pip/commands/help.py
@@ -1,5 +1,7 @@
-from pip.basecommand import Command, command_dict, load_all_commands
-from pip.exceptions import InstallationError
+from pip.basecommand import (Command, command_dict,
+ load_all_commands, SUCCESS,
+ ERROR)
+from pip.exceptions import CommandError
from pip.baseparser import parser
@@ -14,10 +16,10 @@ class HelpCommand(Command):
## FIXME: handle errors better here
command = args[0]
if command not in command_dict:
- raise InstallationError('No command with the name: %s' % command)
+ raise CommandError('No command with the name: %s' % command)
command = command_dict[command]
command.parser.print_help()
- return
+ return SUCCESS
parser.print_help()
print('\nCommands available:')
commands = list(set(command_dict.values()))
@@ -26,6 +28,6 @@ class HelpCommand(Command):
if command.hidden:
continue
print(' %s: %s' % (command.name, command.summary))
-
+ return SUCCESS
HelpCommand()
diff --git a/pip/commands/install.py b/pip/commands/install.py
index 861c332bf..925d57feb 100644
--- a/pip/commands/install.py
+++ b/pip/commands/install.py
@@ -1,11 +1,14 @@
-import os, sys
+import os
+import sys
+import tempfile
+import shutil
from pip.req import InstallRequirement, RequirementSet
from pip.req import parse_requirements
from pip.log import logger
from pip.locations import build_prefix, src_prefix
from pip.basecommand import Command
from pip.index import PackageFinder
-from pip.exceptions import InstallationError
+from pip.exceptions import InstallationError, CommandError
class InstallCommand(Command):
@@ -79,8 +82,14 @@ class InstallCommand(Command):
'-b', '--build', '--build-dir', '--build-directory',
dest='build_dir',
metavar='DIR',
+ default=build_prefix,
+ help='Unpack packages into DIR (default %default) and build from there')
+ self.parser.add_option(
+ '-t', '--target',
+ dest='target_dir',
+ metavar='DIR',
default=None,
- help='Unpack packages into DIR (default %s) and build from there' % build_prefix)
+ help='Install packages into DIR.')
self.parser.add_option(
'-d', '--download', '--download-dir', '--download-directory',
dest='download_dir',
@@ -97,8 +106,8 @@ class InstallCommand(Command):
'--src', '--source', '--source-dir', '--source-directory',
dest='src_dir',
metavar='DIR',
- default=None,
- help='Check out --editable packages into DIR (default %s)' % src_prefix)
+ default=src_prefix,
+ help='Check out --editable packages into DIR (default %default)')
self.parser.add_option(
'-U', '--upgrade',
@@ -106,6 +115,12 @@ class InstallCommand(Command):
action='store_true',
help='Upgrade all packages to the newest available version')
self.parser.add_option(
+ '--force-reinstall',
+ dest='force_reinstall',
+ action='store_true',
+ help='When upgrading, reinstall all packages even if they are '
+ 'already up-to-date.')
+ self.parser.add_option(
'-I', '--ignore-installed',
dest='ignore_installed',
action='store_true',
@@ -162,10 +177,6 @@ class InstallCommand(Command):
mirrors=options.mirrors)
def run(self, options, args):
- if not options.build_dir:
- options.build_dir = build_prefix
- if not options.src_dir:
- options.src_dir = src_prefix
if options.download_dir:
options.no_install = True
options.ignore_installed = True
@@ -174,6 +185,13 @@ class InstallCommand(Command):
install_options = options.install_options or []
if options.use_user_site:
install_options.append('--user')
+ if options.target_dir:
+ options.ignore_installed = True
+ temp_target_dir = tempfile.mkdtemp()
+ options.target_dir = os.path.abspath(options.target_dir)
+ if os.path.exists(options.target_dir) and not os.path.isdir(options.target_dir):
+ raise CommandError("Target path exists but is not a directory, will not continue.")
+ install_options.append('--home=' + temp_target_dir)
global_options = options.global_options or []
index_urls = [options.index_url] + options.extra_index_urls
if options.no_index:
@@ -189,7 +207,8 @@ class InstallCommand(Command):
download_cache=options.download_cache,
upgrade=options.upgrade,
ignore_installed=options.ignore_installed,
- ignore_dependencies=options.ignore_dependencies)
+ ignore_dependencies=options.ignore_dependencies,
+ force_reinstall=options.force_reinstall)
for name in args:
requirement_set.add_requirement(
InstallRequirement.from_line(name, None))
@@ -199,14 +218,17 @@ class InstallCommand(Command):
for filename in options.requirements:
for req in parse_requirements(filename, finder=finder, options=options):
requirement_set.add_requirement(req)
-
if not requirement_set.has_requirements:
+ opts = {'name': self.name}
if options.find_links:
- raise InstallationError('You must give at least one '
- 'requirement to %s (maybe you meant "pip install %s"?)'
- % (self.name, " ".join(options.find_links)))
- raise InstallationError('You must give at least one requirement '
- 'to %(name)s (see "pip help %(name)s")' % dict(name=self.name))
+ msg = ('You must give at least one requirement to %(name)s '
+ '(maybe you meant "pip %(name)s %(links)s"?)' %
+ dict(opts, links=' '.join(options.find_links)))
+ else:
+ msg = ('You must give at least one requirement '
+ 'to %(name)s (see "pip help %(name)s")' % opts)
+ logger.warn(msg)
+ return
if (options.use_user_site and
sys.version_info < (2, 6)):
@@ -239,8 +261,18 @@ class InstallCommand(Command):
requirement_set.create_bundle(self.bundle_filename)
logger.notify('Created bundle in %s' % self.bundle_filename)
# Clean up
- if not options.no_install:
+ if not options.no_install or options.download_dir:
requirement_set.cleanup_files(bundle=self.bundle)
+ if options.target_dir:
+ if not os.path.exists(options.target_dir):
+ os.makedirs(options.target_dir)
+ lib_dir = os.path.join(temp_target_dir, "lib/python/")
+ for item in os.listdir(lib_dir):
+ shutil.move(
+ os.path.join(lib_dir, item),
+ os.path.join(options.target_dir, item)
+ )
+ shutil.rmtree(temp_target_dir)
return requirement_set
diff --git a/pip/commands/search.py b/pip/commands/search.py
index 1a6bf9c52..9f287e594 100644
--- a/pip/commands/search.py
+++ b/pip/commands/search.py
@@ -2,10 +2,12 @@ import sys
import textwrap
import pkg_resources
import pip.download
-from pip.basecommand import Command
+from pip.basecommand import Command, SUCCESS
from pip.util import get_terminal_size
from pip.log import logger
from pip.backwardcompat import xmlrpclib, reduce, cmp
+from pip.exceptions import CommandError
+from pip.status_codes import NO_MATCHES_FOUND
from distutils.version import StrictVersion, LooseVersion
@@ -25,8 +27,7 @@ class SearchCommand(Command):
def run(self, options, args):
if not args:
- logger.warn('ERROR: Missing required argument (search query).')
- return
+ raise CommandError('Missing required argument (search query).')
query = args
index_url = options.index
@@ -38,6 +39,9 @@ class SearchCommand(Command):
terminal_width = get_terminal_size()[0]
print_results(hits, terminal_width=terminal_width)
+ if pypi_hits:
+ return SUCCESS
+ return NO_MATCHES_FOUND
def search(self, query, index_url):
pypi = xmlrpclib.ServerProxy(index_url, pip.download.xmlrpclib_transport)
@@ -106,7 +110,14 @@ def compare_versions(version1, version2):
return cmp(StrictVersion(version1), StrictVersion(version2))
# in case of abnormal version number, fall back to LooseVersion
except ValueError:
+ pass
+ try:
return cmp(LooseVersion(version1), LooseVersion(version2))
+ except TypeError:
+ # certain LooseVersion comparions raise due to unorderable types,
+ # fallback to string comparison
+ return cmp([str(v) for v in LooseVersion(version1).version],
+ [str(v) for v in LooseVersion(version2).version])
def highest_version(versions):
diff --git a/pip/commands/uninstall.py b/pip/commands/uninstall.py
index 7effd844e..9f2b89121 100644
--- a/pip/commands/uninstall.py
+++ b/pip/commands/uninstall.py
@@ -2,6 +2,7 @@ from pip.req import InstallRequirement, RequirementSet, parse_requirements
from pip.basecommand import Command
from pip.exceptions import InstallationError
+
class UninstallCommand(Command):
name = 'uninstall'
usage = '%prog [OPTIONS] PACKAGE_NAMES ...'
diff --git a/pip/download.py b/pip/download.py
index 87d6ad97a..a31e5d670 100644
--- a/pip/download.py
+++ b/pip/download.py
@@ -9,9 +9,9 @@ import tempfile
from pip.backwardcompat import (md5, copytree, xmlrpclib, urllib, urllib2,
urlparse, string_types, HTTPError)
from pip.exceptions import InstallationError
-from pip.util import (splitext, rmtree,
- format_size, display_path, backup_dir, ask,
- unpack_file, create_download_cache_folder, cache_download)
+from pip.util import (splitext, rmtree, format_size, display_path,
+ backup_dir, ask, ask_path_exists, unpack_file,
+ create_download_cache_folder, cache_download)
from pip.vcs import vcs
from pip.log import logger
@@ -64,6 +64,7 @@ def get_file_content(url, comes_from=None):
_scheme_re = re.compile(r'^(http|https|file):', re.I)
_url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I)
+
class URLOpener(object):
"""
pip's own URL helper that adds HTTP auth and proxy support
@@ -131,7 +132,7 @@ class URLOpener(object):
self.prompting = prompting
proxy = self.get_proxy(proxystr)
if proxy:
- proxy_support = urllib2.ProxyHandler({"http": proxy, "ftp": proxy})
+ proxy_support = urllib2.ProxyHandler({"http": proxy, "ftp": proxy, "https": proxy})
opener = urllib2.build_opener(proxy_support, urllib2.CacheFTPHandler)
urllib2.install_opener(opener)
@@ -387,8 +388,9 @@ def _copy_file(filename, location, content_type, link):
copy = True
download_location = os.path.join(location, link.filename)
if os.path.exists(download_location):
- response = ask('The file %s exists. (i)gnore, (w)ipe, (b)ackup '
- % display_path(download_location), ('i', 'w', 'b'))
+ response = ask_path_exists(
+ 'The file %s exists. (i)gnore, (w)ipe, (b)ackup ' %
+ display_path(download_location), ('i', 'w', 'b'))
if response == 'i':
copy = False
elif response == 'w':
@@ -405,7 +407,7 @@ def _copy_file(filename, location, content_type, link):
logger.notify('Saved %s' % display_path(download_location))
-def unpack_http_url(link, location, download_cache, only_download):
+def unpack_http_url(link, location, download_cache, download_dir=None):
temp_dir = tempfile.mkdtemp('-unpack', 'pip-')
target_url = link.url.split('#', 1)[0]
target_file = None
@@ -449,10 +451,9 @@ def unpack_http_url(link, location, download_cache, only_download):
download_hash = _download_url(resp, link, temp_location)
if link.md5_hash:
_check_md5(download_hash, link)
- if only_download:
- _copy_file(temp_location, location, content_type, link)
- else:
- unpack_file(temp_location, location, content_type, link)
+ if download_dir:
+ _copy_file(temp_location, download_dir, content_type, link)
+ unpack_file(temp_location, location, content_type, link)
if target_file and target_file != temp_location:
cache_download(target_file, temp_location, content_type)
if target_file is None:
diff --git a/pip/exceptions.py b/pip/exceptions.py
index 1ad1a616d..22f554a76 100644
--- a/pip/exceptions.py
+++ b/pip/exceptions.py
@@ -13,5 +13,15 @@ class DistributionNotFound(InstallationError):
"""Raised when a distribution cannot be found to satisfy a requirement"""
+class BestVersionAlreadyInstalled(Exception):
+ """Raised when the most up-to-date version of a package is already
+ installed.
+ """
+
+
class BadCommand(Exception):
"""Raised when virtualenv or a command is not found"""
+
+
+class CommandError(Exception):
+ """Raised when there is an error in command-line arguments"""
diff --git a/pip/index.py b/pip/index.py
index 1b8a52b4f..8e53e44b7 100644
--- a/pip/index.py
+++ b/pip/index.py
@@ -3,18 +3,23 @@
import sys
import os
import re
+import gzip
import mimetypes
-import threading
+try:
+ import threading
+except ImportError:
+ import dummy_threading as threading
import posixpath
import pkg_resources
import random
import socket
import string
+import zlib
from pip.log import logger
from pip.util import Inf
from pip.util import normalize_name, splitext
-from pip.exceptions import DistributionNotFound
-from pip.backwardcompat import (WindowsError,
+from pip.exceptions import DistributionNotFound, BestVersionAlreadyInstalled
+from pip.backwardcompat import (WindowsError, BytesIO,
Queue, httplib, urlparse,
URLError, HTTPError, u,
product, url2pathname)
@@ -170,6 +175,7 @@ class PackageFinder(object):
if applicable_versions[0][1] is Inf:
logger.info('Existing installed version (%s) is most up-to-date and satisfies requirement'
% req.satisfied_by.version)
+ raise BestVersionAlreadyInstalled
else:
logger.info('Existing installed version (%s) satisfies requirement (most up-to-date version is %s)'
% (req.satisfied_by.version, applicable_versions[0][1]))
@@ -182,7 +188,7 @@ class PackageFinder(object):
# We have an existing version, and its the best version
logger.info('Installed version (%s) is most up-to-date (past versions: %s)'
% (req.satisfied_by.version, ', '.join([version for link, version in applicable_versions[1:]]) or 'none'))
- return None
+ raise BestVersionAlreadyInstalled
if len(applicable_versions) > 1:
logger.info('Using version %s (newest of versions: %s)' %
(applicable_versions[0][1], ', '.join([version for link, version in applicable_versions])))
@@ -245,7 +251,7 @@ class PackageFinder(object):
_egg_fragment_re = re.compile(r'#egg=([^&]*)')
_egg_info_re = re.compile(r'([a-z0-9_.]+)-([a-z0-9_.-]+)', re.I)
- _py_version_re = re.compile(r'-py([123]\.[0-9])$')
+ _py_version_re = re.compile(r'-py([123]\.?[0-9]?)$')
def _sort_links(self, links):
"Returns elements of links in order, non-egg links first, egg links second, while eliminating duplicates"
@@ -291,6 +297,11 @@ class PackageFinder(object):
logger.debug('Skipping link %s; unknown archive format: %s' % (link, ext))
self.logged_links.add(link)
return []
+ if "macosx10" in link.path and ext == '.zip':
+ if link not in self.logged_links:
+ logger.debug('Skipping link %s; macosx10 one' % (link))
+ self.logged_links.add(link)
+ return []
version = self._egg_info_matches(egg_info, search_name, link)
if version is None:
logger.debug('Skipping link %s; wrong project name (not %s)' % (link, search_name))
@@ -442,7 +453,15 @@ class HTMLPage(object):
real_url = geturl(resp)
headers = resp.info()
- inst = cls(u(resp.read()), real_url, headers)
+ contents = resp.read()
+ encoding = headers.get('Content-Encoding', None)
+ #XXX need to handle exceptions and add testing for this
+ if encoding is not None:
+ if encoding == 'gzip':
+ contents = gzip.GzipFile(fileobj=BytesIO(contents)).read()
+ if encoding == 'deflate':
+ contents = zlib.decompress(contents)
+ inst = cls(u(contents), real_url, headers)
except (HTTPError, URLError, socket.timeout, socket.error, OSError, WindowsError):
e = sys.exc_info()[1]
desc = str(e)
@@ -539,7 +558,7 @@ class HTMLPage(object):
href_match = self._href_re.search(self.content, pos=match.end())
if not href_match:
continue
- url = match.group(1) or match.group(2) or match.group(3)
+ url = href_match.group(1) or href_match.group(2) or href_match.group(3)
if not url:
continue
url = self.clean_link(urlparse.urljoin(self.base_url, url))
diff --git a/pip/locations.py b/pip/locations.py
index b439bd375..34c6dbbe6 100644
--- a/pip/locations.py
+++ b/pip/locations.py
@@ -46,5 +46,7 @@ else:
default_config_file = os.path.join(default_storage_dir, 'pip.conf')
default_log_file = os.path.join(default_storage_dir, 'pip.log')
# Forcing to use /usr/local/bin for standard Mac OS X framework installs
+ # Also log to ~/Library/Logs/ for use with the Console.app log viewer
if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/':
bin_py = '/usr/local/bin'
+ default_log_file = os.path.join(user_dir, 'Library/Logs/pip.log')
diff --git a/pip/log.py b/pip/log.py
index 56196b787..63541a1c1 100644
--- a/pip/log.py
+++ b/pip/log.py
@@ -4,6 +4,8 @@
import sys
import logging
+import pip.backwardcompat
+
class Logger(object):
@@ -71,7 +73,8 @@ class Logger(object):
## FIXME: should this be a name, not a level number?
rendered = '%02i %s' % (level, rendered)
if hasattr(consumer, 'write'):
- consumer.write(rendered+'\n')
+ rendered += '\n'
+ pip.backwardcompat.fwrite(consumer, rendered)
else:
consumer(rendered)
diff --git a/pip/req.py b/pip/req.py
index 7110abc93..ff423dfa2 100644
--- a/pip/req.py
+++ b/pip/req.py
@@ -6,11 +6,12 @@ import zipfile
import pkg_resources
import tempfile
from pip.locations import bin_py, running_under_virtualenv
-from pip.exceptions import InstallationError, UninstallationError
+from pip.exceptions import (InstallationError, UninstallationError,
+ BestVersionAlreadyInstalled)
from pip.vcs import vcs
from pip.log import logger
from pip.util import display_path, rmtree
-from pip.util import ask, backup_dir
+from pip.util import ask, ask_path_exists, backup_dir
from pip.util import is_installable_dir, is_local, dist_is_local
from pip.util import renames, normalize_path, egg_link_path
from pip.util import make_path_relative
@@ -34,8 +35,10 @@ class InstallRequirement(object):
def __init__(self, req, comes_from, source_dir=None, editable=False,
url=None, update=True):
+ self.extras = ()
if isinstance(req, string_types):
req = pkg_resources.Requirement.parse(req)
+ self.extras = req.extras
self.req = req
self.comes_from = comes_from
self.source_dir = source_dir
@@ -91,15 +94,15 @@ class InstallRequirement(object):
# If the line has an egg= definition, but isn't editable, pull the requirement out.
# Otherwise, assume the name is the req for the non URL/path/archive case.
if link and req is None:
- url = link.url_fragment
- req = link.egg_fragment
+ url = link.url_fragment
+ req = link.egg_fragment
- # Handle relative file URLs
- if link.scheme == 'file' and re.search(r'\.\./', url):
- url = path_to_url(os.path.normpath(os.path.abspath(link.path)))
+ # Handle relative file URLs
+ if link.scheme == 'file' and re.search(r'\.\./', url):
+ url = path_to_url(os.path.normpath(os.path.abspath(link.path)))
else:
- req = name
+ req = name
return cls(req, comes_from, url=url)
@@ -327,11 +330,12 @@ exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec'))
def requirements(self, extras=()):
in_extra = None
for line in self.egg_info_lines('requires.txt'):
- match = self._requirements_section_re.match(line)
+ match = self._requirements_section_re.match(line.lower())
if match:
in_extra = match.group(1)
continue
if in_extra and in_extra not in extras:
+ logger.debug('skipping extra %s' % in_extra)
# Skip requirement for an extra we aren't requiring
continue
yield line
@@ -502,8 +506,9 @@ exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec'))
archive_name = '%s-%s.zip' % (self.name, self.installed_version)
archive_path = os.path.join(build_dir, archive_name)
if os.path.exists(archive_path):
- response = ask('The file %s exists. (i)gnore, (w)ipe, (b)ackup '
- % display_path(archive_path), ('i', 'w', 'b'))
+ response = ask_path_exists(
+ 'The file %s exists. (i)gnore, (w)ipe, (b)ackup ' %
+ display_path(archive_path), ('i', 'w', 'b'))
if response == 'i':
create_archive = False
elif response == 'w':
@@ -768,7 +773,7 @@ class Requirements(object):
return self._dict[key]
def __repr__(self):
- values = [ '%s: %s' % (repr(k), repr(self[k])) for k in self.keys() ]
+ values = ['%s: %s' % (repr(k), repr(self[k])) for k in self.keys()]
return 'Requirements({%s})' % ', '.join(values)
@@ -776,13 +781,14 @@ class RequirementSet(object):
def __init__(self, build_dir, src_dir, download_dir, download_cache=None,
upgrade=False, ignore_installed=False,
- ignore_dependencies=False):
+ ignore_dependencies=False, force_reinstall=False):
self.build_dir = build_dir
self.src_dir = src_dir
self.download_dir = download_dir
self.download_cache = download_cache
self.upgrade = upgrade
self.ignore_installed = ignore_installed
+ self.force_reinstall = force_reinstall
self.requirements = Requirements()
# Mapping of alias: real_name
self.requirement_aliases = {}
@@ -903,18 +909,35 @@ class RequirementSet(object):
else:
req_to_install = reqs.pop(0)
install = True
+ best_installed = False
if not self.ignore_installed and not req_to_install.editable:
req_to_install.check_if_exists()
if req_to_install.satisfied_by:
if self.upgrade:
- req_to_install.conflicts_with = req_to_install.satisfied_by
- req_to_install.satisfied_by = None
+ if not self.force_reinstall:
+ try:
+ url = finder.find_requirement(
+ req_to_install, self.upgrade)
+ except BestVersionAlreadyInstalled:
+ best_installed = True
+ install = False
+ else:
+ # Avoid the need to call find_requirement again
+ req_to_install.url = url.url
+
+ if not best_installed:
+ req_to_install.conflicts_with = req_to_install.satisfied_by
+ req_to_install.satisfied_by = None
else:
install = False
if req_to_install.satisfied_by:
- logger.notify('Requirement already satisfied '
- '(use --upgrade to upgrade): %s'
- % req_to_install)
+ if best_installed:
+ logger.notify('Requirement already up-to-date: %s'
+ % req_to_install)
+ else:
+ logger.notify('Requirement already satisfied '
+ '(use --upgrade to upgrade): %s'
+ % req_to_install)
if req_to_install.editable:
logger.notify('Obtaining %s' % req_to_install)
elif install:
@@ -948,6 +971,7 @@ class RequirementSet(object):
location = req_to_install.build_location(self.build_dir, not self.is_download)
## FIXME: is the existance of the checkout good enough to use it? I don't think so.
unpack = True
+ url = None
if not os.path.exists(os.path.join(location, 'setup.py')):
## FIXME: this won't upgrade when there's an existing package unpacked in `location`
if req_to_install.url is None:
@@ -970,7 +994,6 @@ class RequirementSet(object):
unpack = False
if unpack:
is_bundle = req_to_install.is_bundle
- url = None
if is_bundle:
req_to_install.move_bundle_files(self.build_dir, self.src_dir)
for subreq in req_to_install.bundle_requirements():
@@ -978,8 +1001,8 @@ class RequirementSet(object):
self.add_requirement(subreq)
elif self.is_download:
req_to_install.source_dir = location
+ req_to_install.run_egg_info()
if url and url.scheme in vcs.all_schemes:
- req_to_install.run_egg_info()
req_to_install.archive(self.download_dir)
else:
req_to_install.source_dir = location
@@ -1002,12 +1025,13 @@ class RequirementSet(object):
req_to_install.satisfied_by = None
else:
install = False
- if not is_bundle and not self.is_download:
+ if not is_bundle:
## FIXME: shouldn't be globally added:
finder.add_dependency_links(req_to_install.dependency_links)
- ## FIXME: add extras in here:
+ if (req_to_install.extras):
+ logger.notify("Installing extra requirements: %r" % ','.join(req_to_install.extras))
if not self.ignore_dependencies:
- for req in req_to_install.requirements():
+ for req in req_to_install.requirements(req_to_install.extras):
try:
name = pkg_resources.Requirement.parse(req).project_name
except ValueError:
@@ -1023,8 +1047,11 @@ class RequirementSet(object):
self.add_requirement(subreq)
if req_to_install.name not in self.requirements:
self.requirements[req_to_install.name] = req_to_install
+ if self.is_download:
+ self.reqs_to_cleanup.append(req_to_install)
else:
self.reqs_to_cleanup.append(req_to_install)
+
if install:
self.successfully_downloaded.append(req_to_install)
if bundle and (req_to_install.url and req_to_install.url.startswith('file:///')):
@@ -1044,6 +1071,7 @@ class RequirementSet(object):
remove_dir.append(self.build_dir)
# The source dir of a bundle can always be removed.
+ # FIXME: not if it pre-existed the bundle!
if bundle:
remove_dir.append(self.src_dir)
@@ -1068,20 +1096,25 @@ class RequirementSet(object):
def unpack_url(self, link, location, only_download=False):
if only_download:
- location = self.download_dir
+ loc = self.download_dir
+ else:
+ loc = location
if is_vcs_url(link):
- return unpack_vcs_link(link, location, only_download)
+ return unpack_vcs_link(link, loc, only_download)
elif is_file_url(link):
- return unpack_file_url(link, location)
+ return unpack_file_url(link, loc)
else:
if self.download_cache:
self.download_cache = os.path.expanduser(self.download_cache)
- return unpack_http_url(link, location, self.download_cache, only_download)
+ retval = unpack_http_url(link, location, self.download_cache, self.download_dir)
+ if only_download:
+ _write_delete_marker_message(os.path.join(location, PIP_DELETE_MARKER_FILENAME))
+ return retval
def install(self, install_options, global_options=()):
"""Install everything in this set (after having downloaded and unpacked the packages)"""
to_install = [r for r in self.requirements.values()
- if self.upgrade or not r.satisfied_by]
+ if not r.satisfied_by]
if to_install:
logger.notify('Installing collected packages: %s' % ', '.join([req.name for req in to_install]))
@@ -1222,7 +1255,7 @@ def parse_requirements(filename, finder=None, comes_from=None, options=None):
req_url = line[len('--requirement'):].strip().strip('=')
if _scheme_re.search(filename):
# Relative to a URL
- req_url = urlparse.urljoin(req_url, filename)
+ req_url = urlparse.urljoin(filename, req_url)
elif not _scheme_re.search(req_url):
req_url = os.path.join(os.path.dirname(filename), req_url)
for item in parse_requirements(req_url, finder, comes_from=filename, options=options):
diff --git a/pip/status_codes.py b/pip/status_codes.py
new file mode 100644
index 000000000..b6208e964
--- /dev/null
+++ b/pip/status_codes.py
@@ -0,0 +1,5 @@
+SUCCESS = 0
+ERROR = 1
+UNKNOWN_ERROR = 2
+VIRTUALENV_NOT_FOUND = 3
+NO_MATCHES_FOUND = 23
diff --git a/pip/util.py b/pip/util.py
index 5d0ed4f88..e5ad6df17 100644
--- a/pip/util.py
+++ b/pip/util.py
@@ -98,10 +98,18 @@ def find_command(cmd, paths=None, pathext=None):
def get_pathext(default_pathext=None):
"""Returns the path extensions from environment or a default"""
if default_pathext is None:
- default_pathext = os.pathsep.join([ '.COM', '.EXE', '.BAT', '.CMD' ])
+ default_pathext = os.pathsep.join(['.COM', '.EXE', '.BAT', '.CMD'])
pathext = os.environ.get('PATHEXT', default_pathext)
return pathext
+
+def ask_path_exists(message, options):
+ for action in os.environ.get('PIP_EXISTS_ACTION', ''):
+ if action in options:
+ return action
+ return ask(message, options)
+
+
def ask(message, options):
"""Ask the message interactively, with the given possible responses"""
while 1:
@@ -424,6 +432,17 @@ def untar_file(filename, location):
if member.isdir():
if not os.path.exists(path):
os.makedirs(path)
+ elif member.issym():
+ try:
+ tar._extract_member(member, path)
+ except:
+ e = sys.exc_info()[1]
+ # Some corrupt tar files seem to produce this
+ # (specifically bad symlinks)
+ logger.warn(
+ 'In the tar file %s the member %s is invalid: %s'
+ % (filename, member.name, e))
+ continue
else:
try:
fp = tar.extractfile(member)
diff --git a/pip/vcs/__init__.py b/pip/vcs/__init__.py
index 33c9c7c5d..a2137e96a 100644
--- a/pip/vcs/__init__.py
+++ b/pip/vcs/__init__.py
@@ -5,7 +5,8 @@ import shutil
from pip.backwardcompat import urlparse, urllib
from pip.log import logger
-from pip.util import display_path, backup_dir, find_command, ask, rmtree
+from pip.util import (display_path, backup_dir, find_command,
+ ask, rmtree, ask_path_exists)
__all__ = ['vcs', 'get_src_requirement']
@@ -182,27 +183,34 @@ class VersionControl(object):
if os.path.exists(os.path.join(dest, self.dirname)):
existing_url = self.get_url(dest)
if self.compare_urls(existing_url, url):
- logger.info('%s in %s exists, and has correct URL (%s)'
- % (self.repo_name.title(), display_path(dest), url))
- logger.notify('Updating %s %s%s'
- % (display_path(dest), self.repo_name, rev_display))
+ logger.info('%s in %s exists, and has correct URL (%s)' %
+ (self.repo_name.title(), display_path(dest),
+ url))
+ logger.notify('Updating %s %s%s' %
+ (display_path(dest), self.repo_name,
+ rev_display))
self.update(dest, rev_options)
else:
- logger.warn('%s %s in %s exists with URL %s'
- % (self.name, self.repo_name, display_path(dest), existing_url))
- prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ', ('s', 'i', 'w', 'b'))
+ logger.warn('%s %s in %s exists with URL %s' %
+ (self.name, self.repo_name,
+ display_path(dest), existing_url))
+ prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ',
+ ('s', 'i', 'w', 'b'))
else:
- logger.warn('Directory %s already exists, and is not a %s %s.'
- % (dest, self.name, self.repo_name))
+ logger.warn('Directory %s already exists, '
+ 'and is not a %s %s.' %
+ (dest, self.name, self.repo_name))
prompt = ('(i)gnore, (w)ipe, (b)ackup ', ('i', 'w', 'b'))
if prompt:
- logger.warn('The plan is to install the %s repository %s'
- % (self.name, url))
- response = ask('What to do? %s' % prompt[0], prompt[1])
+ logger.warn('The plan is to install the %s repository %s' %
+ (self.name, url))
+ response = ask_path_exists('What to do? %s' % prompt[0],
+ prompt[1])
if response == 's':
- logger.notify('Switching %s %s to %s%s'
- % (self.repo_name, display_path(dest), url, rev_display))
+ logger.notify('Switching %s %s to %s%s' %
+ (self.repo_name, display_path(dest), url,
+ rev_display))
self.switch(dest, url, rev_options)
elif response == 'i':
# do nothing
diff --git a/pip/vcs/bazaar.py b/pip/vcs/bazaar.py
index 9f5389835..5d5277771 100644
--- a/pip/vcs/bazaar.py
+++ b/pip/vcs/bazaar.py
@@ -2,6 +2,7 @@ import os
import tempfile
import re
from pip import call_subprocess
+from pip.backwardcompat import urlparse
from pip.log import logger
from pip.util import rmtree, display_path
from pip.vcs import vcs, VersionControl
@@ -13,10 +14,15 @@ class Bazaar(VersionControl):
dirname = '.bzr'
repo_name = 'branch'
bundle_file = 'bzr-branch.txt'
- schemes = ('bzr', 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp', 'bzr+ftp')
+ schemes = ('bzr', 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp', 'bzr+ftp', 'bzr+lp')
guide = ('# This was a Bazaar branch; to make it a branch again run:\n'
'bzr branch -r %(rev)s %(url)s .\n')
+ def __init__(self, url=None, *args, **kwargs):
+ super(Bazaar, self).__init__(url, *args, **kwargs)
+ urlparse.non_hierarchical.extend(['lp'])
+ urlparse.uses_fragment.extend(['lp'])
+
def parse_vcs_bundle_file(self, content):
url = rev = None
for line in content.splitlines():
diff --git a/pip/vcs/git.py b/pip/vcs/git.py
index c159d7744..ecaf19f50 100644
--- a/pip/vcs/git.py
+++ b/pip/vcs/git.py
@@ -8,6 +8,7 @@ from pip.backwardcompat import url2pathname, urlparse
urlsplit = urlparse.urlsplit
urlunsplit = urlparse.urlunsplit
+
class Git(VersionControl):
name = 'git'
dirname = '.git'
diff --git a/pip/vcs/subversion.py b/pip/vcs/subversion.py
index 79c31ec0e..f54eee664 100644
--- a/pip/vcs/subversion.py
+++ b/pip/vcs/subversion.py
@@ -1,6 +1,7 @@
import os
import re
-from pip import call_subprocess
+from pip.backwardcompat import urlparse
+from pip import call_subprocess, InstallationError
from pip.index import Link
from pip.util import rmtree, display_path
from pip.log import logger
@@ -10,6 +11,8 @@ _svn_xml_url_re = re.compile('url="([^"]+)"')
_svn_rev_re = re.compile('committed-rev="(\d+)"')
_svn_url_re = re.compile(r'URL: (.+)')
_svn_revision_re = re.compile(r'Revision: (.+)')
+_svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"')
+_svn_info_xml_url_re = re.compile(r'<url>(.*)</url>')
class Subversion(VersionControl):
@@ -54,6 +57,7 @@ class Subversion(VersionControl):
def export(self, location):
"""Export the svn repository at the url to the destination location"""
url, rev = self.get_url_rev()
+ rev_options = get_rev_options(url, rev)
logger.notify('Exporting svn repository %s to %s' % (url, location))
logger.indent += 2
try:
@@ -62,7 +66,7 @@ class Subversion(VersionControl):
# --force fixes this, but was only added in svn 1.5
rmtree(location)
call_subprocess(
- [self.cmd, 'export', url, location],
+ [self.cmd, 'export'] + rev_options + [url, location],
filter_stdout=self._filter, show_stdout=False)
finally:
logger.indent -= 2
@@ -77,11 +81,10 @@ class Subversion(VersionControl):
def obtain(self, dest):
url, rev = self.get_url_rev()
+ rev_options = get_rev_options(url, rev)
if rev:
- rev_options = ['-r', rev]
rev_display = ' (to revision %s)' % rev
else:
- rev_options = []
rev_display = ''
if self.check_destination(dest, url, rev_options, rev_display):
logger.notify('Checking out %s%s to %s'
@@ -119,33 +122,12 @@ class Subversion(VersionControl):
if not os.path.exists(entries_fn):
## FIXME: should we warn?
continue
- f = open(entries_fn)
- data = f.read()
- f.close()
-
- if data.startswith('8') or data.startswith('9') or data.startswith('10'):
- data = list(map(str.splitlines, data.split('\n\x0c\n')))
- del data[0][0] # get rid of the '8'
- dirurl = data[0][3]
- revs = [int(d[9]) for d in data if len(d)>9 and d[9]]+[0]
- if revs:
- localrev = max(revs)
- else:
- localrev = 0
- elif data.startswith('<?xml'):
- dirurl = _svn_xml_url_re.search(data).group(1) # get repository URL
- revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)]+[0]
- if revs:
- localrev = max(revs)
- else:
- localrev = 0
- else:
- logger.warn("Unrecognized .svn/entries format; skipping %s", base)
- dirs[:] = []
- continue
+
+ dirurl, localrev = self._get_svn_url_rev(base)
+
if base == location:
base_url = dirurl+'/' # save the root url
- elif not dirurl.startswith(base_url):
+ elif not dirurl or not dirurl.startswith(base_url):
dirs[:] = []
continue # not part of the same svn tree, skip it
revision = max(revision, localrev)
@@ -170,22 +152,39 @@ class Subversion(VersionControl):
logger.warn("Could not find setup.py for directory %s (tried all parent directories)"
% orig_location)
return None
+
+ return self._get_svn_url_rev(location)[0]
+
+ def _get_svn_url_rev(self, location):
f = open(os.path.join(location, self.dirname, 'entries'))
data = f.read()
f.close()
if data.startswith('8') or data.startswith('9') or data.startswith('10'):
data = list(map(str.splitlines, data.split('\n\x0c\n')))
del data[0][0] # get rid of the '8'
- return data[0][3]
+ url = data[0][3]
+ revs = [int(d[9]) for d in data if len(d)>9 and d[9]]+[0]
elif data.startswith('<?xml'):
match = _svn_xml_url_re.search(data)
if not match:
raise ValueError('Badly formatted data: %r' % data)
- return match.group(1) # get repository URL
+ url = match.group(1) # get repository URL
+ revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)]+[0]
else:
- logger.warn("Unrecognized .svn/entries format in %s" % location)
- # Or raise exception?
- return None
+ try:
+ # subversion >= 1.7
+ xml = call_subprocess([self.cmd, 'info', '--xml', location], show_stdout=False)
+ url = _svn_info_xml_url_re.search(xml).group(1)
+ revs = [int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml)]
+ except InstallationError:
+ url, revs = None, []
+
+ if revs:
+ rev = max(revs)
+ else:
+ rev = 0
+
+ return url, rev
def get_tag_revs(self, svn_tag_url):
stdout = call_subprocess(
@@ -241,4 +240,33 @@ class Subversion(VersionControl):
full_egg_name = '%s-dev_r%s' % (egg_project_name, rev)
return 'svn+%s@%s#egg=%s' % (repo, rev, full_egg_name)
+
+def get_rev_options(url, rev):
+ if rev:
+ rev_options = ['-r', rev]
+ else:
+ rev_options = []
+
+ r = urlparse.urlsplit(url)
+ if hasattr(r, 'username'):
+ # >= Python-2.5
+ username, password = r.username, r.password
+ else:
+ netloc = r[1]
+ if '@' in netloc:
+ auth = netloc.split('@')[0]
+ if ':' in auth:
+ username, password = auth.split(':', 1)
+ else:
+ username, password = auth, None
+ else:
+ username, password = None, None
+
+ if username:
+ rev_options += ['--username', username]
+ if password:
+ rev_options += ['--password', password]
+ return rev_options
+
+
vcs.register(Subversion)
diff --git a/pip/venv.py b/pip/venv.py
deleted file mode 100644
index 88de4f6de..000000000
--- a/pip/venv.py
+++ /dev/null
@@ -1,53 +0,0 @@
-"""Tools for working with virtualenv environments"""
-
-import os
-import sys
-import subprocess
-from pip.exceptions import BadCommand
-from pip.log import logger
-
-
-def restart_in_venv(venv, base, site_packages, args):
- """
- Restart this script using the interpreter in the given virtual environment
- """
- if base and not os.path.isabs(venv) and not venv.startswith('~'):
- base = os.path.expanduser(base)
- # ensure we have an abs basepath at this point:
- # a relative one makes no sense (or does it?)
- if os.path.isabs(base):
- venv = os.path.join(base, venv)
-
- if venv.startswith('~'):
- venv = os.path.expanduser(venv)
-
- if not os.path.exists(venv):
- try:
- import virtualenv
- except ImportError:
- print('The virtual environment does not exist: %s' % venv)
- print('and virtualenv is not installed, so a new environment cannot be created')
- sys.exit(3)
- print('Creating new virtualenv environment in %s' % venv)
- virtualenv.logger = logger
- logger.indent += 2
- virtualenv.create_environment(venv, site_packages=site_packages)
- if sys.platform == 'win32':
- python = os.path.join(venv, 'Scripts', 'python.exe')
- # check for bin directory which is used in buildouts
- if not os.path.exists(python):
- python = os.path.join(venv, 'bin', 'python.exe')
- else:
- python = os.path.join(venv, 'bin', 'python')
- if not os.path.exists(python):
- python = venv
- if not os.path.exists(python):
- raise BadCommand('Cannot find virtual environment interpreter at %s' % python)
- base = os.path.dirname(os.path.dirname(python))
- file = os.path.join(os.path.dirname(__file__), 'runner.py')
- if file.endswith('.pyc'):
- file = file[:-1]
- proc = subprocess.Popen(
- [python, file] + args + [base, '___VENV_RESTART___'])
- proc.wait()
- sys.exit(proc.returncode)
diff --git a/setup.py b/setup.py
index 9fab856e4..322762117 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ import os
from setuptools import setup
# If you change this version, change it also in docs/conf.py
-version = "1.0.2"
+version = "1.1"
doc_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "docs")
index_filename = os.path.join(doc_dir, "index.txt")
@@ -51,5 +51,5 @@ setup(name="pip",
packages=['pip', 'pip.commands', 'pip.vcs'],
entry_points=dict(console_scripts=['pip=pip:main', 'pip-%s=pip:main' % sys.version[:3]]),
test_suite='nose.collector',
- tests_require=['nose', 'virtualenv>=1.6', 'scripttest>=1.1.1', 'mock'],
+ tests_require=['nose', 'virtualenv>=1.7', 'scripttest>=1.1.1', 'mock'],
zip_safe=False)
diff --git a/tests/packages/BrokenEmitsUTF8/broken.py b/tests/packages/BrokenEmitsUTF8/broken.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/packages/BrokenEmitsUTF8/broken.py
diff --git a/tests/packages/BrokenEmitsUTF8/setup.py b/tests/packages/BrokenEmitsUTF8/setup.py
new file mode 100644
index 000000000..989cc2a0b
--- /dev/null
+++ b/tests/packages/BrokenEmitsUTF8/setup.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from distutils.core import setup
+import sys
+
+class FakeError(Exception):
+ pass
+
+if sys.argv[1] == 'install':
+ if hasattr(sys.stdout, 'buffer'):
+ sys.stdout.buffer.write('\nThis package prints out UTF-8 stuff like:\n'.encode('utf-8'))
+ sys.stdout.buffer.write('* return type of ‘main’ is not ‘int’\n'.encode('utf-8'))
+ sys.stdout.buffer.write('* Björk Guðmundsdóttir [ˈpjœr̥k ˈkvʏðmʏntsˌtoʊhtɪr]'.encode('utf-8'))
+ else:
+ pass
+ sys.stdout.write('\nThis package prints out UTF-8 stuff like:\n')
+ sys.stdout.write('* return type of \xe2\x80\x98main\xe2\x80\x99 is not \xe2\x80\x98int\xe2\x80\x99\n')
+ sys.stdout.write('* Bj\xc3\xb6rk Gu\xc3\xb0mundsd\xc3\xb3ttir [\xcb\x88pj\xc5\x93r\xcc\xa5k \xcb\x88kv\xca\x8f\xc3\xb0m\xca\x8fnts\xcb\x8cto\xca\x8aht\xc9\xaar]\n')
+
+ raise FakeError('this package designed to fail on install')
+
+setup(name='broken',
+ version='0.2broken',
+ py_modules=['broken'],
+ )
diff --git a/tests/packages/pkgwithmpkg-1.0-py2.7-macosx10.7.mpkg.zip b/tests/packages/pkgwithmpkg-1.0-py2.7-macosx10.7.mpkg.zip
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/packages/pkgwithmpkg-1.0-py2.7-macosx10.7.mpkg.zip
diff --git a/tests/packages/pkgwithmpkg-1.0.tar.gz b/tests/packages/pkgwithmpkg-1.0.tar.gz
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/packages/pkgwithmpkg-1.0.tar.gz
diff --git a/tests/path.py b/tests/path.py
index d564ac607..fc724487c 100644
--- a/tests/path.py
+++ b/tests/path.py
@@ -15,6 +15,7 @@ _base = os.path.supports_unicode_filenames and unicode or str
from pip.util import rmtree
+
class Path(_base):
""" Models a path in an object oriented way. """
@@ -32,7 +33,7 @@ class Path(_base):
""" path_obj / 'bc.d' """
""" path_obj / path_obj2 """
return Path(self, path)
-
+
__truediv__ = __div__
def __rdiv__(self, path):
diff --git a/tests/test_basic.py b/tests/test_basic.py
index aa1129745..8bdce9772 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -7,7 +7,7 @@ from os.path import abspath, join, curdir, pardir
from nose import SkipTest
from nose.tools import assert_raises
-from mock import Mock, patch
+from mock import patch
from pip.util import rmtree, find_command
from pip.exceptions import BadCommand
@@ -17,6 +17,7 @@ from tests.test_pip import (here, reset_env, run_pip, pyversion, mkdir,
from tests.local_repos import local_checkout
from tests.path import Path
+
def test_correct_pip_version():
"""
Check we are running proper version of pip in run_pip.
@@ -223,10 +224,10 @@ def test_install_editable_from_git():
reset_env()
args = ['install']
args.extend(['-e',
- '%s#egg=django-feedutil' %
- local_checkout('git+http://github.com/jezdez/django-feedutil.git')])
+ '%s#egg=pip-test-package' %
+ local_checkout('git+http://github.com/pypa/pip-test-package.git')])
result = run_pip(*args, **{"expect_error": True})
- result.assert_installed('django-feedutil', with_files=['.git'])
+ result.assert_installed('pip-test-package', with_files=['.git'])
def test_install_editable_from_hg():
@@ -377,7 +378,7 @@ def test_install_subversion_usersite_editable_with_setuptools_fails():
# We don't try to use setuptools for 3.X.
elif sys.version_info >= (3,):
raise SkipTest()
- env = reset_env()
+ env = reset_env(use_distribute=False)
no_site_packages = env.lib_path/'no-global-site-packages.txt'
if os.path.isfile(no_site_packages):
no_site_packages.rm() # this re-enables user_site
@@ -388,6 +389,7 @@ def test_install_subversion_usersite_editable_with_setuptools_fails():
expect_error=True)
assert '--user --editable not supported with setuptools, use distribute' in result.stdout
+
def test_install_pardir():
"""
Test installing parent directory ('..').
@@ -419,6 +421,7 @@ def test_install_with_pax_header():
run_from = abspath(join(here, 'packages'))
run_pip('install', 'paxpkg.tar.bz2', cwd=run_from)
+
def test_install_using_install_option_and_editable():
"""
Test installing a tool using -e and --install-option
@@ -502,6 +505,7 @@ def test_install_folder_using_relative_path():
egg_folder = env.site_packages / 'mock-100.1-py%s.egg-info' % pyversion
assert egg_folder in result.files_created, str(result)
+
def test_install_package_which_contains_dev_in_name():
"""
Test installing package from pypi which contains 'dev' in name
@@ -513,6 +517,17 @@ def test_install_package_which_contains_dev_in_name():
assert devserver_folder in result.files_created, str(result.stdout)
assert egg_info_folder in result.files_created, str(result)
+
+def test_install_package_with_target():
+ """
+ Test installing a package using pip install --target
+ """
+ env = reset_env()
+ target_dir = env.scratch_path/'target'
+ result = run_pip('install', '-t', target_dir, "initools==0.1")
+ assert Path('scratch')/'target'/'initools' in result.files_created, str(result)
+
+
def test_find_command_folder_in_path():
"""
If a folder named e.g. 'git' is in PATH, and find_command is looking for
@@ -520,13 +535,16 @@ def test_find_command_folder_in_path():
looking.
"""
env = reset_env()
- mkdir('path_one'); path_one = env.scratch_path/'path_one'
+ mkdir('path_one')
+ path_one = env.scratch_path/'path_one'
mkdir(path_one/'foo')
- mkdir('path_two'); path_two = env.scratch_path/'path_two'
+ mkdir('path_two')
+ path_two = env.scratch_path/'path_two'
write_file(path_two/'foo', '# nothing')
found_path = find_command('foo', map(str, [path_one, path_two]))
assert found_path == path_two/'foo'
+
def test_does_not_find_command_because_there_is_no_path():
"""
Test calling `pip.utils.find_command` when there is no PATH env variable
@@ -534,16 +552,18 @@ def test_does_not_find_command_because_there_is_no_path():
environ_before = os.environ
os.environ = {}
try:
- try:
- find_command('anycommand')
- except BadCommand:
- e = sys.exc_info()[1]
- assert e.args == ("Cannot find command 'anycommand'",)
- else:
- raise AssertionError("`find_command` should raise `BadCommand`")
+ try:
+ find_command('anycommand')
+ except BadCommand:
+ e = sys.exc_info()[1]
+ assert e.args == ("Cannot find command 'anycommand'",)
+ else:
+ raise AssertionError("`find_command` should raise `BadCommand`")
finally:
os.environ = environ_before
+
+@patch('os.pathsep', ':')
@patch('pip.util.get_pathext')
@patch('os.path.isfile')
def test_find_command_trys_all_pathext(mock_isfile, getpath_mock):
@@ -552,22 +572,18 @@ def test_find_command_trys_all_pathext(mock_isfile, getpath_mock):
exist.
"""
mock_isfile.return_value = False
- # Patching os.pathsep failed on type checking
- old_sep = os.pathsep
- os.pathsep = ':'
getpath_mock.return_value = os.pathsep.join([".COM", ".EXE"])
- paths = [ os.path.join('path_one', f) for f in ['foo.com', 'foo.exe', 'foo'] ]
- expected = [ ((p,),) for p in paths ]
+ paths = [os.path.join('path_one', f) for f in ['foo.com', 'foo.exe', 'foo']]
+ expected = [((p,),) for p in paths]
+
+ assert_raises(BadCommand, find_command, 'foo', 'path_one')
+ assert mock_isfile.call_args_list == expected, "Actual: %s\nExpected %s" % (mock_isfile.call_args_list, expected)
+ assert getpath_mock.called, "Should call get_pathext"
- try:
- assert_raises(BadCommand, find_command, 'foo', 'path_one')
- assert mock_isfile.call_args_list == expected, "Actual: %s\nExpected %s" % (mock_isfile.call_args_list, expected)
- assert getpath_mock.called, "Should call get_pathext"
- finally:
- os.pathsep = old_sep
+@patch('os.pathsep', ':')
@patch('pip.util.get_pathext')
@patch('os.path.isfile')
def test_find_command_trys_supplied_pathext(mock_isfile, getpath_mock):
@@ -575,19 +591,13 @@ def test_find_command_trys_supplied_pathext(mock_isfile, getpath_mock):
If pathext supplied find_command should use all of its list of extensions to find file.
"""
mock_isfile.return_value = False
- # Patching os.pathsep failed on type checking
- old_sep = os.pathsep
- os.pathsep = ':'
getpath_mock.return_value = ".FOO"
pathext = os.pathsep.join([".RUN", ".CMD"])
- paths = [ os.path.join('path_one', f) for f in ['foo.run', 'foo.cmd', 'foo'] ]
- expected = [ ((p,),) for p in paths ]
+ paths = [os.path.join('path_one', f) for f in ['foo.run', 'foo.cmd', 'foo']]
+ expected = [((p,),) for p in paths]
- try:
- assert_raises(BadCommand, find_command, 'foo', 'path_one', pathext)
- assert mock_isfile.call_args_list == expected, "Actual: %s\nExpected %s" % (mock_isfile.call_args_list, expected)
- assert not getpath_mock.called, "Should not call get_pathext"
- finally:
- os.pathsep = old_sep
+ assert_raises(BadCommand, find_command, 'foo', 'path_one', pathext)
+ assert mock_isfile.call_args_list == expected, "Actual: %s\nExpected %s" % (mock_isfile.call_args_list, expected)
+ assert not getpath_mock.called, "Should not call get_pathext"
diff --git a/tests/test_cleanup.py b/tests/test_cleanup.py
index 8dbc45129..15a050833 100644
--- a/tests/test_cleanup.py
+++ b/tests/test_cleanup.py
@@ -1,11 +1,11 @@
import os
import textwrap
from os.path import abspath, exists, join
-from tests.test_pip import (here, reset_env, run_pip, write_file, mkdir,
- pyversion)
+from tests.test_pip import (here, reset_env, run_pip, write_file, mkdir)
from tests.local_repos import local_checkout
from tests.path import Path
+
def test_cleanup_after_install_from_pypi():
"""
Test clean up after installing a package from PyPI.
@@ -49,6 +49,7 @@ def test_cleanup_after_install_from_local_directory():
assert not exists(build), "unexpected build/ dir exists: %s" % build
assert not exists(src), "unexpected src/ dir exist: %s" % src
+
def test_cleanup_after_create_bundle():
"""
Test clean up after making a bundle. Make sure (build|src)-bundle/ dirs are removed but not src/.
@@ -58,8 +59,8 @@ def test_cleanup_after_create_bundle():
# Install an editable to create a src/ dir.
args = ['install']
args.extend(['-e',
- '%s#egg=django-feedutil' %
- local_checkout('git+http://github.com/jezdez/django-feedutil.git')])
+ '%s#egg=pip-test-package' %
+ local_checkout('git+http://github.com/pypa/pip-test-package.git')])
run_pip(*args)
build = env.venv_path/"build"
src = env.venv_path/"src"
diff --git a/tests/test_config.py b/tests/test_config.py
index 027442e5d..477f868f2 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -33,6 +33,41 @@ def test_command_line_options_override_env_vars():
assert "Getting page http://download.zope.org/ppix" in result.stdout
+def test_env_vars_override_config_file():
+ """
+ Test that environmental variables override settings in config files.
+
+ """
+ fd, config_file = tempfile.mkstemp('-pip.cfg', 'test-')
+ try:
+ _test_env_vars_override_config_file(config_file)
+ finally:
+ # `os.close` is a workaround for a bug in subprocess
+ # http://bugs.python.org/issue3210
+ os.close(fd)
+ os.remove(config_file)
+
+
+def _test_env_vars_override_config_file(config_file):
+ environ = clear_environ(os.environ.copy())
+ environ['PIP_CONFIG_FILE'] = config_file # set this to make pip load it
+ reset_env(environ)
+ # It's important that we test this particular config value ('no-index')
+ # because their is/was a bug which only shows up in cases in which
+ # 'config-item' and 'config_item' hash to the same value modulo the size
+ # of the config dictionary.
+ write_file(config_file, textwrap.dedent("""\
+ [global]
+ no-index = 1
+ """))
+ result = run_pip('install', '-vvv', 'INITools', expect_error=True)
+ assert "DistributionNotFound: No distributions at all found for INITools" in result.stdout
+ environ['PIP_NO_INDEX'] = '0'
+ reset_env(environ)
+ result = run_pip('install', '-vvv', 'INITools', expect_error=True)
+ assert "Successfully installed INITools" in result.stdout
+
+
def test_command_line_append_flags():
"""
Test command line flags that append to defaults set by environmental variables.
diff --git a/tests/test_download.py b/tests/test_download.py
index b52318af5..5d4923ccd 100644
--- a/tests/test_download.py
+++ b/tests/test_download.py
@@ -1,3 +1,5 @@
+from pip.backwardcompat import any
+
import textwrap
from tests.test_pip import reset_env, run_pip, write_file
from tests.path import Path
@@ -26,3 +28,16 @@ def test_single_download_from_requirements_file():
result = run_pip('install', '-r', env.scratch_path/ 'test-req.txt', '-d', '.', expect_error=True)
assert Path('scratch')/ 'INITools-0.1.tar.gz' in result.files_created
assert env.site_packages/ 'initools' not in result.files_created
+
+
+def test_download_should_download_dependencies():
+ """
+ It should download dependencies (in the scratch path)
+ """
+
+ env = reset_env()
+ result = run_pip('install', 'Paste[openid]==1.7.5.1', '-d', '.', expect_error=True)
+ assert Path('scratch')/ 'Paste-1.7.5.1.tar.gz' in result.files_created
+ openid_tarball_prefix = str(Path('scratch')/ 'python-openid-')
+ assert any(path.startswith(openid_tarball_prefix) for path in result.files_created)
+ assert env.site_packages/ 'openid' not in result.files_created
diff --git a/tests/test_extras.py b/tests/test_extras.py
new file mode 100644
index 000000000..163893e87
--- /dev/null
+++ b/tests/test_extras.py
@@ -0,0 +1,27 @@
+from os.path import join
+
+from tests.test_pip import reset_env, run_pip
+
+
+def test_simple_extras_install_from_pypi():
+ """
+ Test installing a package from PyPI using extras dependency Paste[openid].
+ """
+ e = reset_env()
+ result = run_pip('install', 'Paste[openid]==1.7.5.1', expect_stderr=True)
+ initools_folder = e.site_packages / 'openid'
+ assert initools_folder in result.files_created, result.files_created
+
+
+def test_no_extras_uninstall():
+ """
+ No extras dependency gets uninstalled when the root package is uninstalled
+ """
+ env = reset_env()
+ result = run_pip('install', 'Paste[openid]==1.7.5.1', expect_stderr=True)
+ assert join(env.site_packages, 'paste') in result.files_created, sorted(result.files_created.keys())
+ assert join(env.site_packages, 'openid') in result.files_created, sorted(result.files_created.keys())
+ result2 = run_pip('uninstall', 'Paste', '-y')
+ # openid should not be uninstalled
+ initools_folder = env.site_packages / 'openid'
+ assert not initools_folder in result2.files_deleted, result.files_deleted
diff --git a/tests/test_finder.py b/tests/test_finder.py
new file mode 100644
index 000000000..4c930ed81
--- /dev/null
+++ b/tests/test_finder.py
@@ -0,0 +1,18 @@
+from pip.backwardcompat import urllib
+
+from pip.req import InstallRequirement
+from pip.index import PackageFinder
+
+from tests.path import Path
+from tests.test_pip import here
+
+find_links = 'file://' + urllib.quote(str(Path(here).abspath/'packages').replace('\\', '/'))
+
+
+def test_no_mpkg():
+ """Finder skips zipfiles with "macosx10" in the name."""
+ finder = PackageFinder([find_links], [])
+ req = InstallRequirement.from_line("pkgwithmpkg")
+ found = finder.find_requirement(req, False)
+
+ assert found.url.endswith("pkgwithmpkg-1.0.tar.gz"), found
diff --git a/tests/test_freeze.py b/tests/test_freeze.py
index c89ae8b15..4412c1435 100644
--- a/tests/test_freeze.py
+++ b/tests/test_freeze.py
@@ -32,6 +32,7 @@ def _check_output(result, expected):
return '\n========== %s ==========\n' % msg
assert checker.check_output(expected, actual, ELLIPSIS), banner('EXPECTED')+expected+banner('ACTUAL')+actual+banner(6*'=')
+
def test_freeze_basic():
"""
Some tests of freeze, first we have to install some stuff. Note that
@@ -57,6 +58,7 @@ def test_freeze_basic():
<BLANKLINE>""")
_check_output(result, expected)
+
def test_freeze_svn():
"""Now lets try it with an svn checkout"""
env = reset_env()
@@ -64,12 +66,12 @@ def test_freeze_svn():
local_repo('svn+http://svn.colorstudy.com/INITools/trunk'),
'initools-trunk')
result = env.run('python', 'setup.py', 'develop',
- cwd=env.scratch_path/ 'initools-trunk')
+ cwd=env.scratch_path/ 'initools-trunk', expect_stderr=True)
result = run_pip('freeze', expect_stderr=True)
expected = textwrap.dedent("""\
Script result: ...pip freeze
-- stdout: --------------------
- -e %s@10#egg=INITools-0.3.1dev_r10-py...-dev_r10
+ -e %s@10#egg=INITools-0.3.1dev...-dev_r10
...""" % local_checkout('svn+http://svn.colorstudy.com/INITools/trunk'))
_check_output(result, expected)
@@ -80,28 +82,28 @@ def test_freeze_git_clone():
"""
env = reset_env()
- result = env.run('git', 'clone', local_repo('git+http://github.com/jezdez/django-pagination.git'), 'django-pagination')
- result = env.run('git', 'checkout', '1df6507872d73ee387eb375428eafbfc253dfcd8',
- cwd=env.scratch_path/'django-pagination', expect_stderr=True)
+ result = env.run('git', 'clone', local_repo('git+http://github.com/pypa/pip-test-package.git'), 'pip-test-package')
+ result = env.run('git', 'checkout', '7d654e66c8fa7149c165ddeffa5b56bc06619458',
+ cwd=env.scratch_path / 'pip-test-package', expect_stderr=True)
result = env.run('python', 'setup.py', 'develop',
- cwd=env.scratch_path / 'django-pagination')
+ cwd=env.scratch_path / 'pip-test-package')
result = run_pip('freeze', expect_stderr=True)
expected = textwrap.dedent("""\
Script result: ...pip freeze
-- stdout: --------------------
- -e %s@...#egg=django_pagination-...
- ...""" % local_checkout('git+http://github.com/jezdez/django-pagination.git'))
+ -e %s@...#egg=pip_test_package-...
+ ...""" % local_checkout('git+http://github.com/pypa/pip-test-package.git'))
_check_output(result, expected)
result = run_pip('freeze', '-f',
- '%s#egg=django_pagination' % local_checkout('git+http://github.com/jezdez/django-pagination.git'),
+ '%s#egg=pip_test_package' % local_checkout('git+http://github.com/pypa/pip-test-package.git'),
expect_stderr=True)
expected = textwrap.dedent("""\
- Script result: pip freeze -f %(repo)s#egg=django_pagination
+ Script result: pip freeze -f %(repo)s#egg=pip_test_package
-- stdout: --------------------
- -f %(repo)s#egg=django_pagination
- -e %(repo)s@...#egg=django_pagination-dev
- ...""" % {'repo': local_checkout('git+http://github.com/jezdez/django-pagination.git')})
+ -f %(repo)s#egg=pip_test_package
+ -e %(repo)s@...#egg=pip_test_package-dev
+ ...""" % {'repo': local_checkout('git+http://github.com/pypa/pip-test-package.git')})
_check_output(result, expected)
@@ -201,3 +203,39 @@ def test_freeze_with_local_option():
INITools==0.2
<BLANKLINE>""")
_check_output(result, expected)
+
+
+def test_freeze_with_requirement_option():
+ """
+ Test that new requirements are created correctly with --requirement hints
+
+ """
+ reset_env()
+ ignores = textwrap.dedent("""\
+ # Unchanged requirements below this line
+ -r ignore.txt
+ --requirement ignore.txt
+ -Z ignore
+ --always-unzip ignore
+ -f http://ignore
+ -i http://ignore
+ --extra-index-url http://ignore
+ --find-links http://ignore
+ --index-url http://ignore
+ """)
+ write_file('hint.txt', textwrap.dedent("""\
+ INITools==0.1
+ NoExist==4.2
+ """) + ignores)
+ result = run_pip('install', 'initools==0.2')
+ result = run_pip('install', 'MarkupSafe')
+ result = run_pip('freeze', '--requirement', 'hint.txt', expect_stderr=True)
+ expected = textwrap.dedent("""\
+ Script result: pip freeze --requirement hint.txt
+ -- stderr: --------------------
+ Requirement file contains NoExist==4.2, but that package is not installed
+
+ -- stdout: --------------------
+ INITools==0.2
+ """) + ignores + "## The following requirements were added by pip --freeze:..."
+ _check_output(result, expected)
diff --git a/tests/test_help.py b/tests/test_help.py
new file mode 100644
index 000000000..e638963e7
--- /dev/null
+++ b/tests/test_help.py
@@ -0,0 +1,66 @@
+from pip.exceptions import CommandError
+from pip.commands.help import (HelpCommand,
+ SUCCESS,
+ ERROR,)
+from mock import Mock
+from nose.tools import assert_raises
+from tests.test_pip import run_pip, reset_env
+
+
+def test_run_method_should_return_sucess_when_finds_command_name():
+ """
+ Test HelpCommand.run for existing command
+ """
+ options_mock = Mock()
+ args = ('freeze',)
+ help_cmd = HelpCommand()
+ status = help_cmd.run(options_mock, args)
+ assert status == SUCCESS
+
+
+def test_run_method_should_return_sucess_when_command_name_not_specified():
+ """
+ Test HelpCommand.run when there are no args
+ """
+ options_mock = Mock()
+ args = ()
+ help_cmd = HelpCommand()
+ status = help_cmd.run(options_mock, args)
+ assert status == SUCCESS
+
+
+def test_run_method_should_raise_command_error_when_command_does_not_exist():
+ """
+ Test HelpCommand.run for non-existing command
+ """
+ options_mock = Mock()
+ args = ('mycommand',)
+ help_cmd = HelpCommand()
+ assert_raises(CommandError, help_cmd.run, options_mock, args)
+
+
+def test_help_command_should_exit_status_ok_when_command_exists():
+ """
+ Test `help` command for existing command
+ """
+ reset_env()
+ result = run_pip('help', 'freeze')
+ assert result.returncode == SUCCESS
+
+
+def test_help_command_should_exit_status_ok_when_no_command_is_specified():
+ """
+ Test `help` command for no command
+ """
+ reset_env()
+ result = run_pip('help')
+ assert result.returncode == SUCCESS
+
+
+def test_help_command_should_exit_status_error_when_command_does_not_exist():
+ """
+ Test `help` command for non-existing command
+ """
+ reset_env()
+ result = run_pip('help', 'mycommand', expect_error=True)
+ assert result.returncode == ERROR
diff --git a/tests/test_index.py b/tests/test_index.py
index e5385c452..6f9d216dc 100644
--- a/tests/test_index.py
+++ b/tests/test_index.py
@@ -1,4 +1,5 @@
-from pip.index import package_to_requirement
+from pip.index import package_to_requirement, HTMLPage
+
def test_package_name_should_be_converted_to_requirement():
"""
@@ -6,4 +7,22 @@ def test_package_name_should_be_converted_to_requirement():
"""
assert package_to_requirement('Foo-1.2') == 'Foo==1.2'
assert package_to_requirement('Foo-dev') == 'Foo==dev'
- assert package_to_requirement('Foo') == 'Foo' \ No newline at end of file
+ assert package_to_requirement('Foo') == 'Foo'
+
+
+def test_html_page_should_be_able_to_scrap_rel_links():
+ """
+ Test scraping page looking for url in href
+ """
+ page = HTMLPage("""
+ <!-- The <th> elements below are a terrible terrible hack for setuptools -->
+ <li>
+ <strong>Home Page:</strong>
+ <!-- <th>Home Page -->
+ <a href="http://supervisord.org/">http://supervisord.org/</a>
+ </li>""", "supervisor")
+
+ links = list(page.scraped_rel_links())
+ assert len(links) == 1
+ assert links[0].url == 'http://supervisord.org/'
+
diff --git a/tests/test_pip.py b/tests/test_pip.py
index ec3ebc9b1..17e8f6616 100644
--- a/tests/test_pip.py
+++ b/tests/test_pip.py
@@ -49,15 +49,11 @@ sys.path = [src_folder] + sys.path
def create_virtualenv(where, distribute=False):
- save_argv = sys.argv
-
- try:
- import virtualenv
- distribute_opt = distribute and ['--distribute'] or []
- sys.argv = ['virtualenv', '--quiet'] + distribute_opt + ['--no-site-packages', '--unzip-setuptools', where]
- virtualenv.main()
- finally:
- sys.argv = save_argv
+ import virtualenv
+ if sys.version_info[0] > 2:
+ distribute = True
+ virtualenv.create_environment(
+ where, use_distribute=distribute, unzip_setuptools=True)
return virtualenv.path_locations(where)
@@ -293,9 +289,10 @@ class TestPipEnvironment(TestFileEnvironment):
if use_distribute is None:
use_distribute = os.environ.get('PIP_TEST_USE_DISTRIBUTE', False)
+ self.use_distribute = use_distribute
# Create a virtualenv and remember where it's putting things.
- virtualenv_paths = create_virtualenv(self.venv_path, distribute=use_distribute)
+ virtualenv_paths = create_virtualenv(self.venv_path, distribute=self.use_distribute)
assert self.venv_path == virtualenv_paths[0] # sanity check
@@ -329,7 +326,7 @@ class TestPipEnvironment(TestFileEnvironment):
" rather than expected %r" % (pythonbin, self.bin_path/'python'))
# make sure we have current setuptools to avoid svn incompatibilities
- if not use_distribute:
+ if not self.use_distribute:
install_setuptools(self)
# Uninstall whatever version of pip came with the virtualenv.
@@ -421,6 +418,8 @@ class FastTestPipEnvironment(TestPipEnvironment):
# put the test-scratch virtualenv's bin dir first on the PATH
self.environ['PATH'] = Path.pathsep.join((self.bin_path, self.environ['PATH']))
+ self.use_distribute = os.environ.get('PIP_TEST_USE_DISTRIBUTE', False)
+
if self.root_path.exists:
rmtree(self.root_path)
if self.backup_path.exists:
@@ -429,10 +428,8 @@ class FastTestPipEnvironment(TestPipEnvironment):
demand_dirs(self.venv_path)
demand_dirs(self.scratch_path)
- use_distribute = os.environ.get('PIP_TEST_USE_DISTRIBUTE', False)
-
# Create a virtualenv and remember where it's putting things.
- create_virtualenv(self.venv_path, distribute=use_distribute)
+ create_virtualenv(self.venv_path, distribute=self.use_distribute)
demand_dirs(self.user_site_path)
@@ -449,7 +446,7 @@ class FastTestPipEnvironment(TestPipEnvironment):
" rather than expected %r" % (pythonbin, self.bin_path/'python'))
# make sure we have current setuptools to avoid svn incompatibilities
- if not use_distribute:
+ if not self.use_distribute:
install_setuptools(self)
# Uninstall whatever version of pip came with the virtualenv.
@@ -468,6 +465,7 @@ class FastTestPipEnvironment(TestPipEnvironment):
def __del__(self):
pass # shutil.rmtree(str(self.root_path), ignore_errors=True)
+
def run_pip(*args, **kw):
result = env.run('pip', *args, **kw)
ignore = []
diff --git a/tests/test_requirements.py b/tests/test_requirements.py
index d40f5e872..59e1347c3 100644
--- a/tests/test_requirements.py
+++ b/tests/test_requirements.py
@@ -6,6 +6,7 @@ from tests.test_pip import reset_env, run_pip, write_file, pyversion, here, path
from tests.local_repos import local_checkout
from tests.path import Path
+
def test_requirements_file():
"""
Test installing from a requirements file.
@@ -25,6 +26,7 @@ def test_requirements_file():
fn = '%s-%s-py%s.egg-info' % (other_lib_name, other_lib_version, pyversion)
assert result.files_created[env.site_packages/fn].dir
+
def test_relative_requirements_file():
"""
Test installing from a requirements file with a relative path with an egg= definition..
@@ -39,6 +41,7 @@ def test_relative_requirements_file():
assert (env.site_packages/'FSPkg-0.1dev-py%s.egg-info' % pyversion) in result.files_created, str(result)
assert (env.site_packages/'fspkg') in result.files_created, str(result.stdout)
+
def test_multiple_requirements_files():
"""
Test installing from multiple nested requirements files.
@@ -65,7 +68,7 @@ def test_respect_order_in_requirements_file():
write_file('frameworks-req.txt', textwrap.dedent("""\
bidict
ordereddict
- mock
+ initools
"""))
result = run_pip('install', '-r', env.scratch_path / 'frameworks-req.txt')
downloaded = [line for line in result.stdout.split('\n')
@@ -75,8 +78,8 @@ def test_respect_order_in_requirements_file():
'be "bidict" but was "%s"' % downloaded[0]
assert 'ordereddict' in downloaded[1], 'Second download should ' \
'be "ordereddict" but was "%s"' % downloaded[1]
- assert 'mock' in downloaded[2], 'Third download should ' \
- 'be "mock" but was "%s"' % downloaded[2]
+ assert 'initools' in downloaded[2], 'Third download should ' \
+ 'be "initools" but was "%s"' % downloaded[2]
def test_requirements_data_structure_keeps_order():
@@ -88,6 +91,7 @@ def test_requirements_data_structure_keeps_order():
assert ['pip', 'nose', 'coverage'] == list(requirements.values())
assert ['pip', 'nose', 'coverage'] == list(requirements.keys())
+
def test_requirements_data_structure_implements__repr__():
requirements = Requirements()
requirements['pip'] = 'pip'
@@ -95,6 +99,7 @@ def test_requirements_data_structure_implements__repr__():
assert "Requirements({'pip': 'pip', 'nose': 'nose'})" == repr(requirements)
+
def test_requirements_data_structure_implements__contains__():
requirements = Requirements()
requirements['pip'] = 'pip'
diff --git a/tests/test_search.py b/tests/test_search.py
index 7a4a9ef12..53aad5349 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -2,7 +2,8 @@ import pip.download
from pip.commands.search import (compare_versions,
highest_version,
transform_hits,
- SearchCommand,)
+ SearchCommand)
+from pip.status_codes import NO_MATCHES_FOUND, SUCCESS
from pip.backwardcompat import xmlrpclib, b
from mock import Mock
from tests.test_pip import run_pip, reset_env, pyversion
@@ -23,6 +24,7 @@ def test_version_compare():
assert compare_versions('1.0', '1.1') == -1
assert compare_versions('1.1', '1.0') == 1
assert compare_versions('1.1a1', '1.1') == -1
+ assert compare_versions('1.1.1', '1.1a') == -1
assert highest_version(['1.0', '2.0', '0.1']) == '2.0'
assert highest_version(['1.0a1', '1.0']) == '1.0'
@@ -65,6 +67,7 @@ def test_searching_through_Search_class():
"""
Verify if ``pip.vcs.Search`` uses tests xmlrpclib.Transport class
"""
+ original_xmlrpclib_transport = pip.download.xmlrpclib_transport
pip.download.xmlrpclib_transport = fake_transport = Mock()
query = 'mylittlequerythatdoesnotexists'
dumped_xmlrpc_request = b(xmlrpclib.dumps(({'name': query, 'summary': query}, 'or'), 'search'))
@@ -72,5 +75,57 @@ def test_searching_through_Search_class():
fake_transport.request.return_value = (expected,)
pypi_searcher = SearchCommand()
result = pypi_searcher.search(query, 'http://pypi.python.org/pypi')
- assert expected == result, result
- fake_transport.request.assert_called_with('pypi.python.org', '/pypi', dumped_xmlrpc_request, verbose=VERBOSE_FALSE)
+ try:
+ assert expected == result, result
+ fake_transport.request.assert_called_with('pypi.python.org', '/pypi', dumped_xmlrpc_request, verbose=VERBOSE_FALSE)
+ finally:
+ pip.download.xmlrpclib_transport = original_xmlrpclib_transport
+
+
+def test_search_missing_argument():
+ """
+ Test missing required argument for search
+ """
+ env = reset_env(use_distribute=True)
+ result = run_pip('search', expect_error=True)
+ assert 'ERROR: Missing required argument (search query).' in result.stdout
+
+
+def test_run_method_should_return_sucess_when_find_packages():
+ """
+ Test SearchCommand.run for found package
+ """
+ options_mock = Mock()
+ options_mock.index = 'http://pypi.python.org/pypi'
+ search_cmd = SearchCommand()
+ status = search_cmd.run(options_mock, ('pip',))
+ assert status == SUCCESS
+
+
+def test_run_method_should_return_no_matches_found_when_does_not_find_packages():
+ """
+ Test SearchCommand.run for no matches
+ """
+ options_mock = Mock()
+ options_mock.index = 'http://pypi.python.org/pypi'
+ search_cmd = SearchCommand()
+ status = search_cmd.run(options_mock, ('non-existant-package',))
+ assert status == NO_MATCHES_FOUND, status
+
+
+def test_search_should_exit_status_code_zero_when_find_packages():
+ """
+ Test search exit status code for package found
+ """
+ env = reset_env(use_distribute=True)
+ result = run_pip('search', 'pip')
+ assert result.returncode == SUCCESS
+
+
+def test_search_exit_status_code_when_finds_no_package():
+ """
+ Test search exit status code for no matches
+ """
+ env = reset_env(use_distribute=True)
+ result = run_pip('search', 'non-existant-package', expect_error=True)
+ assert result.returncode == NO_MATCHES_FOUND
diff --git a/tests/test_unicode.py b/tests/test_unicode.py
new file mode 100644
index 000000000..d9196e750
--- /dev/null
+++ b/tests/test_unicode.py
@@ -0,0 +1,25 @@
+import os
+from tests.test_pip import here, reset_env, run_pip
+
+
+def test_install_package_that_emits_unicode():
+ """
+ Install a package with a setup.py that emits UTF-8 output and then fails.
+ This works fine in Python 2, but fails in Python 3 with:
+
+ Traceback (most recent call last):
+ ...
+ File "/Users/marc/python/virtualenvs/py3.1-phpserialize/lib/python3.2/site-packages/pip-1.0.2-py3.2.egg/pip/__init__.py", line 230, in call_subprocess
+ line = console_to_str(stdout.readline())
+ File "/Users/marc/python/virtualenvs/py3.1-phpserialize/lib/python3.2/site-packages/pip-1.0.2-py3.2.egg/pip/backwardcompat.py", line 60, in console_to_str
+ return s.decode(console_encoding)
+ UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 17: ordinal not in range(128)
+
+ Refs https://github.com/pypa/pip/issues/326
+ """
+
+ env = reset_env()
+ to_install = os.path.abspath(os.path.join(here, 'packages', 'BrokenEmitsUTF8'))
+ result = run_pip('install', to_install, expect_error=True)
+ assert '__main__.FakeError: this package designed to fail on install' in result.stdout
+ assert 'UnicodeDecodeError' not in result.stdout
diff --git a/tests/test_uninstall.py b/tests/test_uninstall.py
index 1b35bffc3..c88c7a681 100644
--- a/tests/test_uninstall.py
+++ b/tests/test_uninstall.py
@@ -7,6 +7,7 @@ from tests.local_repos import local_repo, local_checkout
from pip.util import rmtree
+
def test_simple_uninstall():
"""
Test simple install and uninstall.
diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py
index 4d2145bb8..c6b8d686a 100644
--- a/tests/test_upgrade.py
+++ b/tests/test_upgrade.py
@@ -41,6 +41,34 @@ def test_upgrade_if_requested():
assert env.site_packages/'INITools-0.1-py%s.egg-info' % pyversion not in result.files_created
+def test_upgrade_with_newest_already_installed():
+ """
+ If the newest version of a package is already installed, the package should
+ not be reinstalled and the user should be informed.
+ """
+
+ env = reset_env()
+ run_pip('install', 'INITools')
+ result = run_pip('install', '--upgrade', 'INITools')
+ assert not result.files_created, 'pip install --upgrade INITools upgraded when it should not have'
+ assert 'already up-to-date' in result.stdout
+
+
+def test_upgrade_force_reinstall_newest():
+ """
+ Force reinstallation of a package even if it is already at its newest
+ version if --force-reinstall is supplied.
+ """
+
+ env = reset_env()
+ result = run_pip('install', 'INITools')
+ assert env.site_packages/ 'initools' in result.files_created, sorted(result.files_created.keys())
+ result2 = run_pip('install', '--upgrade', '--force-reinstall', 'INITools')
+ assert result2.files_updated, 'upgrade to INITools 0.3 failed'
+ result3 = run_pip('uninstall', 'initools', '-y', expect_error=True)
+ assert_all_changes(result, result3, [env.venv/'build', 'cache'])
+
+
def test_uninstall_before_upgrade():
"""
Automatic uninstall-before-upgrade.
@@ -54,6 +82,7 @@ def test_uninstall_before_upgrade():
result3 = run_pip('uninstall', 'initools', '-y', expect_error=True)
assert_all_changes(result, result3, [env.venv/'build', 'cache'])
+
def test_uninstall_before_upgrade_from_url():
"""
Automatic uninstall-before-upgrade from URL.
@@ -67,6 +96,7 @@ def test_uninstall_before_upgrade_from_url():
result3 = run_pip('uninstall', 'initools', '-y', expect_error=True)
assert_all_changes(result, result3, [env.venv/'build', 'cache'])
+
def test_upgrade_to_same_version_from_url():
"""
When installing from a URL the same version that is already installed, no
@@ -81,6 +111,7 @@ def test_upgrade_to_same_version_from_url():
result3 = run_pip('uninstall', 'initools', '-y', expect_error=True)
assert_all_changes(result, result3, [env.venv/'build', 'cache'])
+
def test_upgrade_from_reqs_file():
"""
Upgrade from a requirements file.
@@ -134,6 +165,7 @@ def test_editable_git_upgrade():
version2 = env.run('version_pkg')
assert 'some different version' in version2.stdout
+
def test_should_not_install_always_from_cache():
"""
If there is an old cached package, pip should download the newer version
@@ -146,6 +178,7 @@ def test_should_not_install_always_from_cache():
assert env.site_packages/'INITools-0.2-py%s.egg-info' % pyversion not in result.files_created
assert env.site_packages/'INITools-0.1-py%s.egg-info' % pyversion in result.files_created
+
def test_install_with_ignoreinstalled_requested():
"""
It installs package if ignore installed is set.
@@ -153,6 +186,7 @@ def test_install_with_ignoreinstalled_requested():
"""
env = reset_env()
run_pip('install', 'INITools==0.1', expect_error=True)
- result = run_pip('install', '-I' , 'INITools', expect_error=True)
+ result = run_pip('install', '-I', 'INITools', expect_error=True)
assert result.files_created, 'pip install -I did not install'
assert env.site_packages/'INITools-0.1-py%s.egg-info' % pyversion not in result.files_created
+
diff --git a/tests/test_vcs_backends.py b/tests/test_vcs_backends.py
index fcdd00dac..9561254fa 100644
--- a/tests/test_vcs_backends.py
+++ b/tests/test_vcs_backends.py
@@ -2,16 +2,17 @@ from tests.test_pip import (reset_env, run_pip,
_create_test_package, _change_test_package_version)
from tests.local_repos import local_checkout
+
def test_install_editable_from_git_with_https():
"""
Test cloning from Git with https.
"""
reset_env()
result = run_pip('install', '-e',
- '%s#egg=django-feedutil' %
- local_checkout('git+https://github.com/jezdez/django-feedutil.git'),
+ '%s#egg=pip-test-package' %
+ local_checkout('git+https://github.com/pypa/pip-test-package.git'),
expect_error=True)
- result.assert_installed('django-feedutil', with_files=['.git'])
+ result.assert_installed('pip-test-package', with_files=['.git'])
def test_git_with_sha1_revisions():
@@ -58,15 +59,15 @@ def test_git_with_tag_name_and_update():
Test cloning a git repository and updating to a different version.
"""
reset_env()
- result = run_pip('install', '-e', '%s#egg=django-staticfiles' %
- local_checkout('git+http://github.com/jezdez/django-staticfiles.git'),
+ result = run_pip('install', '-e', '%s#egg=pip-test-package' %
+ local_checkout('git+http://github.com/pypa/pip-test-package.git'),
expect_error=True)
- result.assert_installed('django-staticfiles', with_files=['.git'])
+ result.assert_installed('pip-test-package', with_files=['.git'])
result = run_pip('install', '--global-option=--version', '-e',
- '%s@0.3.1#egg=django-staticfiles' %
- local_checkout('git+http://github.com/jezdez/django-staticfiles.git'),
+ '%s@0.1.1#egg=pip-test-package' %
+ local_checkout('git+http://github.com/pypa/pip-test-package.git'),
expect_error=True)
- assert '0.3.1\n' in result.stdout
+ assert '0.1.1\n' in result.stdout
def test_git_branch_should_not_be_changed():
@@ -75,12 +76,13 @@ def test_git_branch_should_not_be_changed():
related to issue #32 and #161
"""
env = reset_env()
- run_pip('install', '-e', '%s#egg=django-staticfiles' %
- local_checkout('git+http://github.com/jezdez/django-staticfiles.git'),
+ run_pip('install', '-e', '%s#egg=pip-test-package' %
+ local_checkout('git+http://github.com/pypa/pip-test-package.git'),
expect_error=True)
- source_dir = env.venv_path/'src'/'django-staticfiles'
+ source_dir = env.venv_path/'src'/'pip-test-package'
result = env.run('git', 'branch', cwd=source_dir)
- assert '* master' in result.stdout
+ assert '* master' in result.stdout, result.stdout
+
def test_git_with_non_editable_unpacking():
"""
@@ -88,9 +90,10 @@ def test_git_with_non_editable_unpacking():
"""
reset_env()
result = run_pip('install', '--global-option=--version', local_checkout(
- 'git+http://github.com/jezdez/django-staticfiles.git@0.3.1#egg=django-staticfiles'
+ 'git+http://github.com/pypa/pip-test-package.git@0.1.1#egg=pip-test-package'
), expect_error=True)
- assert '0.3.1\n' in result.stdout
+ assert '0.1.1\n' in result.stdout
+
def test_git_with_editable_where_egg_contains_dev_string():
"""
@@ -101,6 +104,7 @@ def test_git_with_editable_where_egg_contains_dev_string():
local_checkout('git+git://github.com/dcramer/django-devserver.git'))
result.assert_installed('django-devserver', with_files=['.git'])
+
def test_git_with_non_editable_where_egg_contains_dev_string():
"""
Test cloning a git repository from a non-editable url which contains "dev" string
diff --git a/tests/test_vcs_bazaar.py b/tests/test_vcs_bazaar.py
new file mode 100644
index 000000000..4e43fe5f9
--- /dev/null
+++ b/tests/test_vcs_bazaar.py
@@ -0,0 +1,29 @@
+from tests.test_pip import pyversion
+from pip.vcs.bazaar import Bazaar
+
+if pyversion >= '3':
+ VERBOSE_FALSE = False
+else:
+ VERBOSE_FALSE = 0
+
+
+def test_bazaar_simple_urls():
+ """
+ Test bzr url support.
+
+ SSH and launchpad have special handling.
+ """
+ http_bzr_repo = Bazaar(url='bzr+http://bzr.myproject.org/MyProject/trunk/#egg=MyProject')
+ https_bzr_repo = Bazaar(url='bzr+https://bzr.myproject.org/MyProject/trunk/#egg=MyProject')
+ ssh_bzr_repo = Bazaar(url='bzr+ssh://bzr.myproject.org/MyProject/trunk/#egg=MyProject')
+ ftp_bzr_repo = Bazaar(url='bzr+ftp://bzr.myproject.org/MyProject/trunk/#egg=MyProject')
+ sftp_bzr_repo = Bazaar(url='bzr+sftp://bzr.myproject.org/MyProject/trunk/#egg=MyProject')
+ launchpad_bzr_repo = Bazaar(url='bzr+lp:MyLaunchpadProject#egg=MyLaunchpadProject')
+
+ assert http_bzr_repo.get_url_rev() == ('http://bzr.myproject.org/MyProject/trunk/', None)
+ assert https_bzr_repo.get_url_rev() == ('https://bzr.myproject.org/MyProject/trunk/', None)
+ assert ssh_bzr_repo.get_url_rev() == ('bzr+ssh://bzr.myproject.org/MyProject/trunk/', None)
+ assert ftp_bzr_repo.get_url_rev() == ('ftp://bzr.myproject.org/MyProject/trunk/', None)
+ assert sftp_bzr_repo.get_url_rev() == ('sftp://bzr.myproject.org/MyProject/trunk/', None)
+ assert launchpad_bzr_repo.get_url_rev() == ('lp:MyLaunchpadProject', None)
+
diff --git a/tests/test_vcs_subversion.py b/tests/test_vcs_subversion.py
new file mode 100644
index 000000000..212220113
--- /dev/null
+++ b/tests/test_vcs_subversion.py
@@ -0,0 +1,21 @@
+from mock import patch
+from pip.vcs.subversion import Subversion
+from tests.test_pip import reset_env
+
+@patch('pip.vcs.subversion.call_subprocess')
+def test_obtain_should_recognize_auth_info_in_url(call_subprocess_mock):
+ env = reset_env()
+ svn = Subversion(url='svn+http://username:password@svn.example.com/')
+ svn.obtain(env.scratch_path/'test')
+ call_subprocess_mock.assert_called_with([
+ svn.cmd, 'checkout', '-q', '--username', 'username', '--password', 'password',
+ 'http://username:password@svn.example.com/', env.scratch_path/'test'])
+
+@patch('pip.vcs.subversion.call_subprocess')
+def test_export_should_recognize_auth_info_in_url(call_subprocess_mock):
+ env = reset_env()
+ svn = Subversion(url='svn+http://username:password@svn.example.com/')
+ svn.export(env.scratch_path/'test')
+ assert call_subprocess_mock.call_args[0] == ([
+ svn.cmd, 'export', '--username', 'username', '--password', 'password',
+ 'http://username:password@svn.example.com/', env.scratch_path/'test'],)