blob: efd928bf8325953b8f19333103069ffb47922d49 [file] [view] [edit]
infra_virtualenv README
=======================
This repository provides a common Python virtualenv interface that
infra code (such as chromite) can depend on. At this point, it is
experimental and not yet used in production.
Virtualenv users should create a `requirements.txt` file listing the
packages that they need and use the wrapper scripts (described below)
to create the virtualenv and run commands within it.
To add packages to this repository, run:
$ pip wheel -w path/to/pip_packages -r path/to/requirements.txt
Commit the changes and make a CL.
For example for chromite, from within `chromite/virtualenv`, run:
$ pip wheel -w pip_packages -r requirements.txt
Wrapper scripts
---------------
`create_venv` creates or updates a virtualenv using a
`requirements.txt` file.
$ create_venv .venv requirements.txt
To run the virtualenv python, use:
$ .venv/bin/python
NOTE: it is not generally safe to run the other scripts in `.venv/bin`
due to the hard-coded paths in the virtualenv. Instead of running
`.venv/bin/pip` for example, use `.venv/bin/python -m pip`.
Here’s a complete example:
$ echo mock==2.0.0 > requirements.txt
$ ./create_venv .venv requirements.txt
$ .venv/bin/python
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.prefix # This points to the virtualenv now
'/usr/local/google/home/ayatane/src/chromiumos/infra_virtualenv/.venv'
>>> import mock
Adding arbitrary directories to import path
-------------------------------------------
NOTE: Do not use this for third party dependencies (stuff not owned by
ChromiumOS)! This should only be used to set up imports for stuff we
own. For example, importing python-MySQL should NOT use this, but
importing chromite from Autotest may use this.
This should be handled by the minimum amount of code in the package's
`__init__.py` file.
Example:
"""Autotest package."""
import sys
# Use the minimum amount of logic to find the path to add
_chromite_parent = 'site-packages'
sys.path.append(_chromite_parent)
A solid understanding of the [Python import system](https://docs.python.org/3/reference/import.html)
is recommended (link is for Python 3, but is informative).
In brief, `__init__.py` is executed whenever the package is imported.
The package is imported before any submodule or subpackage is
imported. The package is only imported once per Python process;
future imports get the "cached" "singleton" package object. Thus,
`__init__.py` will modify `sys.path` exactly once and is guaranteed to
be run before anything in that package is used.
Background for __init__.py recommended usage
--------------------------------------------
(Updated on 2017-02-21)
Previously, we set up the import path for first party modules by
patching sys.path in very creative ways. This tends to cause
problems.
In the original virtualenv design, first party packages would be
handled by using `pip -e` to install the packages inside the
virtualenv in editable mode. However, the implementation of this is
wonky, buggy, and generally considered a second citizen to installing
packages "properly". The blocking issue is that pip -e must copy the
entire source tree internally, all for writing a metadata file and
what amounts to a symlink. This takes a considerable amount of time
for large packages such as chromite (the .git directory is copied
also). This is not trivially fixed upstream, and the editable
installs feature is not considered a top priority.
Thus, the reason for adding our own import path patching is to work
around pip while solving our existing woes.
A test concept of the feature was implemented using .pth files, which
are simple files that contain paths to add to Python’s import path.
The problem with this implementation is that our code is run in a lot
of really weird configurations. Having a single .pth file may not be
good enough. While relative paths are supported (and sane), some of
the places our code is run do not use the same file system hierarchy.
Also, there is no simple way to handle recursive requirements.
Thus, going forward, the standard way to support first party imports
is a small bit of `sys.path` patching code in the respective package’s
`__init__.py` file. This enable the use of Python's full power for
handling weird environments as needed; a little logic goes a long way.
A lot of other things also just work due to `__init__.py` file
semantics: for example, recursive requirements.