Extending Virtualenvwrapper

Long experience with home-grown solutions for customizing adevelopment environment has proven how valuable it can be to have theability to automate common tasks and eliminate persistent annoyances.Carpenters build jigs, software developers write shell scripts.virtualenvwrapper continues the tradition of encouraging a craftsmanto modify their tools to work the way they want, rather than the otherway around.

There are two ways to attach your code so that virtualenvwrapper willrun it: End-users can use shell scripts or other programs for personalcustomization, e.g. automatically performing an action on every newvirtualenv (see Per-User Customization). Extensions can also beimplemented in Python by using Setuptools entry points, making itpossible to share common behaviors between systems and developers.

Use the hooks provided to eliminate repetitive manual operations andstreamline your development workflow. For example, set up thepre_activate and post_activate hooks totrigger an IDE to load a project file to reload files from the lastediting session, manage time-tracking records, or start and stopdevelopment versions of an application server. Use theinitialize hook to add entirely new commands and hooksto virtualenvwrapper. And the pre_mkvirtualenv andpost_mkvirtualenv hooks give you an opportunity toinstall basic requirements into each new development environment,initialize a source code control repository, or otherwise set up a newproject.

Defining an Extension

Note

Virtualenvwrapper is delivered with a plugin for creating andrunning the user customization scripts(user_scripts). The examples below are taken fromthe implementation of that plugin.

Code Organization

The Python package for virtualenvwrapper is a namespace package.That means multiple libraries can install code into the package, evenif they are not distributed together or installed into the samedirectory. Extensions can (optionally) use the virtualenvwrappernamespace by setting up their source tree like:

  • virtualenvwrapper/
    • init.py
    • userscripts.pyAnd placing the following code in _init.py:
  1. """virtualenvwrapper module
  2. """
  3.  
  4. __import__('pkg_resources').declare_namespace(__name__)

Note

Extensions can be loaded from any package, so using thevirtualenvwrapper namespace is not required.

Extension API

After the package is established, the next step is to create a moduleto hold the extension code. For example,virtualenvwrapper/user_scripts.py. The module should contain theactual extension entry points. Supporting code can be included, orimported from elsewhere using standard Python code organizationtechniques.

The API is the same for every extension point. Each uses a Pythonfunction that takes a single argument, a list of strings passed to thehook loader on the command line.

  1. def function_name(args):
  2. # args is a list of strings passed to the hook loader

The contents of the argument list are defined for each extension pointbelow (see Extension Points).

Extension Invocation

Direct Action

Plugins can attach to each hook in two different ways. The default isto have a function run and do some work directly. For example, theinitialize() function for the user scripts plugin creates defaultuser scripts when virtualenvwrapper.sh is loaded.

  1. def initialize(args):
  2. for filename, comment in GLOBAL_HOOKS:
  3. make_hook(os.path.join('$WORKON_HOME', filename), comment)
  4. return

Modifying the User Environment

There are cases where the extension needs to update the user’senvironment (e.g., changing the current working directory or settingenvironment variables). Modifications to the user environment must bemade within the user’s current shell, and cannot be run in a separateprocess. To have code run in the user’s shell process, extensions candefine hook functions to return the text of the shell statements to beexecuted. These source hooks are run after the regular hooks withthe same name, and should not do any work of their own.

The initialize_source() hook for the user scripts plugin looks fora global initialize script and causes it to be run in the currentshell process.

  1. def initialize_source(args):
  2. return """
  3. #
  4. # Run user-provided scripts
  5. #
  6. [ -f "$WORKON_HOME/initialize" ] && source "$WORKON_HOME/initialize"
  7. """

Warning

Because the extension is modifying the user’s working shell, caremust be taken not to corrupt the environment by overwritingexisting variable values unexpectedly. Avoid creating temporaryvariables where possible, and use unique names where variablescannot be avoided. Prefixing variables with the extension name isa good way to manage the namespace. For example, instead oftemp_file use user_scripts_temp_file. Use unset torelease temporary variable names when they are no longer needed.

Warning

virtualenvwrapper works under several shells with slightlydifferent syntax (bash, sh, zsh, ksh). Take this portability intoaccount when defining source hooks. Sticking to the simplestpossible syntax usually avoids problems, but there may be caseswhere examining the SHELL environment variable to generatedifferent syntax for each case is the only way to achieve thedesired result.

Registering Entry Points

The functions defined in the plugin need to be registered as entrypoints in order for virtualenvwrapper’s hook loader to find them.Entry points are configured in the setup.py (or setup.cfg whenusing pbr) for your package by mapping the entry point name to thefunction in the package that implements it.

This partial copy of virtualenvwrapper’s setup.cfg illustrates howthe initialize() and initialize_source() entry points areconfigured.

  1. virtualenvwrapper.initialize =
  2. user_scripts = virtualenvwrapper.user_scripts:initialize
  3. project = virtualenvwrapper.project:initialize
  4. virtualenvwrapper.initialize_source =
  5. user_scripts = virtualenvwrapper.user_scripts:initialize_source
  6. virtualenvwrapper.pre_mkvirtualenv =
  7. user_scripts = virtualenvwrapper.user_scripts:pre_mkvirtualenv
  8. virtualenvwrapper.post_mkvirtualenv_source =
  9. user_scripts = virtualenvwrapper.user_scripts:post_mkvirtualenv_source
  10. virtualenvwrapper.pre_cpvirtualenv =
  11. user_scripts = virtualenvwrapper.user_scripts:pre_cpvirtualenv
  12. virtualenvwrapper.post_cpvirtualenv_source =
  13. user_scripts = virtualenvwrapper.user_scripts:post_cpvirtualenv_source
  14. virtualenvwrapper.pre_rmvirtualenv =
  15. user_scripts = virtualenvwrapper.user_scripts:pre_rmvirtualenv
  16. virtualenvwrapper.post_rmvirtualenv =
  17. user_scripts = virtualenvwrapper.user_scripts:post_rmvirtualenv
  18. virtualenvwrapper.project.pre_mkproject =
  19. project = virtualenvwrapper.project:pre_mkproject
  20. virtualenvwrapper.project.post_mkproject_source =
  21. project = virtualenvwrapper.project:post_mkproject_source
  22. virtualenvwrapper.pre_activate =
  23. user_scripts = virtualenvwrapper.user_scripts:pre_activate
  24. virtualenvwrapper.post_activate_source =
  25. project = virtualenvwrapper.project:post_activate_source
  26. user_scripts = virtualenvwrapper.user_scripts:post_activate_source
  27. virtualenvwrapper.pre_deactivate_source =
  28. user_scripts = virtualenvwrapper.user_scripts:pre_deactivate_source
  29. virtualenvwrapper.post_deactivate_source =
  30. user_scripts = virtualenvwrapper.user_scripts:post_deactivate_source
  31. virtualenvwrapper.get_env_details =
  32. user_scripts = virtualenvwrapper.user_scripts:get_env_details
  33.  
  34. [pbr]
  35. warnerrors = true
  36.  
  37. [wheel]
  38. universal = true
  39.  
  40. [build_sphinx]
  41. source-dir = docs/source
  42. build-dir = docs/build
  43. all_files = 1

The entrypoints section maps the _group names to lists of entrypoint specifiers. A different group name is defined byvirtualenvwrapper for each extension point (seeExtension Points).

The entry point specifiers are strings with the syntax name =package.module:function. By convention, the name of each entrypoint is the plugin name, but that is not required (the names are notused).

See also

The Hook Loader

Extensions are run through a command line application implemented invirtualenvwrapper.hook_loader. Because virtualenvwrapper.shis the primary caller and users do not typically need to run the appdirectly, no separate script is installed. Instead, to run theapplication, use the -m option to the interpreter:

  1. $ python -m virtualenvwrapper.hook_loader -h
  2. Usage: virtualenvwrapper.hook_loader [options] <hook> [<arguments>]
  3.  
  4. Manage hooks for virtualenvwrapper
  5.  
  6. Options:
  7. -h, --help show this help message and exit
  8. -s, --source Print the shell commands to be run in the current
  9. shell
  10. -l, --list Print a list of the plugins available for the given
  11. hook
  12. -v, --verbose Show more information on the console
  13. -q, --quiet Show less information on the console
  14. -n NAMES, --name=NAMES
  15. Only run the hook from the named plugin

To run the extensions for the initialize hook:

  1. $ python -m virtualenvwrapper.hook_loader -v initialize

To get the shell commands for the initialize hook:

  1. $ python -m virtualenvwrapper.hook_loader --source initialize

In practice, rather than invoking the hook loader directly it is moreconvenient to use the shell function, virtualenvwrapper_run_hookto run the hooks in both modes.:

  1. $ virtualenvwrapper_run_hook initialize

All of the arguments given to shell function are passed directly tothe hook loader.

Logging

The hook loader configures logging so that messages are written to$WORKONHOME/hook.log. Messages also may be written to stderr,depending on the verbosity flag. The default is for messages at _info_or higher levels to be written to stderr, and _debug or higher to go tothe log file. Using logging in this way provides a convenientmechanism for users to control the verbosity of extensions.

To use logging from within your extension, simply instantiate a loggerand call its info(), debug() and other methods with themessages.

  1. import logging
  2. log = logging.getLogger(__name__)
  3.  
  4. def pre_mkvirtualenv(args):
  5. log.debug('pre_mkvirtualenv %s', str(args))
  6. # ...

See also

Extension Points

The extension point names for native plugins follow a namingconvention with several parts:virtualenvwrapper.(pre|post)<event>[_source]. The <event>_ isthe action taken by the user or virtualenvwrapper that triggers theextension. (pre|post) indicates whether to call the extensionbefore or after the event. The suffix _source is added forextensions that return shell code instead of taking action directly(see Modifying the User Environment).

get_env_details

The virtualenvwrapper.get_env_details hooks are run whenworkon is run with no arguments and a list of the virtualenvironments is printed. The hook is run once for each environment,after the name is printed, and can be used to show additionalinformation about that environment.

initialize

The virtualenvwrapper.initialize hooks are run each timevirtualenvwrapper.sh is loaded into the user’s environment. Theinitialize hook can be used to install templates for configurationfiles or otherwise prepare the system for proper plugin operation.

pre_mkvirtualenv

The virtualenvwrapper.pre_mkvirtualenv hooks are run after thevirtual environment is created, but before the new environment isactivated. The current working directory for when the hook is run is$WORKON_HOME and the name of the new environment is passed as anargument.

post_mkvirtualenv

The virtualenvwrapper.post_mkvirtualenv hooks are run after a newvirtual environment is created and activated. $VIRTUAL_ENV is setto point to the new environment.

pre_activate

The virtualenvwrapper.pre_activate hooks are run just before anenvironment is enabled. The environment name is passed as the firstargument.

post_activate

The virtualenvwrapper.post_activate hooks are run just after anenvironment is enabled. $VIRTUAL_ENV is set to point to thecurrent environment.

pre_deactivate

The virtualenvwrapper.pre_deactivate hooks are run just before anenvironment is disabled. $VIRTUAL_ENV is set to point to thecurrent environment.

post_deactivate

The virtualenvwrapper.post_deactivate hooks are run just after anenvironment is disabled. The name of the environment just deactivatedis passed as the first argument.

pre_rmvirtualenv

The virtualenvwrapper.pre_rmvirtualenv hooks are run just beforean environment is deleted. The name of the environment being deletedis passed as the first argument.

post_rmvirtualenv

The virtualenvwrapper.post_rmvirtualenv hooks are run just afteran environment is deleted. The name of the environment being deletedis passed as the first argument.

Adding New Extension Points

Plugins that define new operations can also define new extensionpoints. No setup needs to be done to allow the hook loader to findthe extensions; documenting the names and adding calls tovirtualenvwrapper_run_hook is sufficient to cause them to beinvoked.

The hook loader assumes all extension point names start withvirtualenvwrapper. and new plugins will want to use their ownnamespace qualifier to append to that. For example, the projectextension defines new events around creating project directories (preand post). These are calledvirtualenvwrapper.project.pre_mkproject andvirtualenvwrapper.project.post_mkproject. These are invokedwith:

  1. virtualenvwrapper_run_hook project.pre_mkproject $project_name

and:

  1. virtualenvwrapper_run_hook project.post_mkproject

respectively.