|
4 | 4 | Extension Plugins
|
5 | 5 | ===================
|
6 | 6 |
|
7 |
| -virtualenvwrapper adds several hook points you can use to modify its |
8 |
| -behavior. End-users can provide simple shell scripts (see |
9 |
| -:ref:`hook-scripts`). Extensions can also be implemented in Python by |
10 |
| -using the ``setuptools`` style *entry points*. |
| 7 | +virtualenvwrapper adds several extension points for modifying its |
| 8 | +behavior. End-users can use shell scripts or other programs for |
| 9 | +personal customization (see :ref:`scripts`). Extensions can also be |
| 10 | +implemented in Python by using Distribute_ *entry points*, making |
| 11 | +it possible to share common behaviors between systems. |
11 | 12 |
|
12 |
| -Types of Hooks |
13 |
| --------------- |
| 13 | +Defining an Extension |
| 14 | +===================== |
| 15 | + |
| 16 | +.. note:: |
| 17 | + |
| 18 | + Virtualenvwrapper is delivered with a plugin for creating and |
| 19 | + running the user customization scripts. The examples below are |
| 20 | + taken from the implementation of that plugin. |
| 21 | + |
| 22 | +Code Organization |
| 23 | +----------------- |
| 24 | + |
| 25 | +The Python package for ``virtualenvwrapper`` is a *namespace package*. |
| 26 | +That means multiple libraries can install code into the package, even |
| 27 | +if they are not distributed together or installed into the same |
| 28 | +directory. Extensions can (optionally) use the ``virtualenvwrapper`` |
| 29 | +namespace by setting up their source tree like: |
| 30 | + |
| 31 | +* virtualenvwrapper/ |
| 32 | + |
| 33 | + * __init__.py |
| 34 | + * user_scripts.py |
| 35 | + |
| 36 | +And placing the following code in the ``__init__.py``:: |
| 37 | + |
| 38 | + """virtualenvwrapper module |
| 39 | + """ |
| 40 | + |
| 41 | + __import__('pkg_resources').declare_namespace(__name__) |
14 | 42 |
|
15 |
| -Existing Hook Points |
| 43 | +This isn't required, so if you would prefer to use a different |
| 44 | +namespace that will work, too. |
| 45 | + |
| 46 | +Extension API |
| 47 | +------------- |
| 48 | + |
| 49 | +After the package is established, the next step is to create a module |
| 50 | +to hold the extension code. For example, |
| 51 | +``virtualenvwrapper/user_scripts.py``. The module should contain the |
| 52 | +actual extension entry points. Supporting code can be included, or |
| 53 | +imported from elsewhere using standard Python code organization |
| 54 | +techniques. |
| 55 | + |
| 56 | +The API is the same for every extension point. Each uses a Python |
| 57 | +function that takes a single argument, a list of string arguments |
| 58 | +passed to the hook loader on the command line. The contents of the |
| 59 | +argument list are defined for each extension point below (see |
| 60 | +:ref:`plugins-extension-points`). |
| 61 | + |
| 62 | +:: |
| 63 | + |
| 64 | + def function_name(args): |
| 65 | + # args is a list of strings passed to the hook loader |
| 66 | + |
| 67 | +Extension Invocation |
16 | 68 | --------------------
|
17 | 69 |
|
| 70 | +Plugins can attach to each hook in two different ways. The default is |
| 71 | +to have an extension function run and do some work directly. For |
| 72 | +example, the ``initialize()`` hook for the user scripts plugin makes |
| 73 | +sure the scripts exist every time ``virtualenvwrapper.sh`` is loaded. |
| 74 | + |
| 75 | +:: |
| 76 | + |
| 77 | + def initialize(args): |
| 78 | + for filename, comment in GLOBAL_HOOKS: |
| 79 | + make_hook(os.path.join('$WORKON_HOME', filename), comment) |
| 80 | + return |
| 81 | + |
| 82 | +.. _plugins-user-env: |
| 83 | + |
| 84 | +Modifying the User Environment |
| 85 | +------------------------------ |
| 86 | + |
| 87 | +There are cases where the extension needs to update the user's |
| 88 | +environment (e.g., changing the current working directory or setting |
| 89 | +environment variables). Modifications to the user environment must be |
| 90 | +made within the user's current shell, and cannot be run in a separate |
| 91 | +process. Extensions can define hook functions to return the text of |
| 92 | +the shell statements to be executed. These *source* hooks are run |
| 93 | +after the regular hooks with the same name, and should not do any work |
| 94 | +of their own. |
| 95 | + |
| 96 | +The ``initialize_source()`` hook for the user scripts plugin looks for |
| 97 | +a global initialize script and causes it to be run in the current |
| 98 | +shell process. |
| 99 | + |
| 100 | +:: |
| 101 | + |
| 102 | + def initialize_source(args): |
| 103 | + return """ |
| 104 | + # |
| 105 | + # Run user-provided scripts |
| 106 | + # |
| 107 | + [ -f "$WORKON_HOME/initialize" ] && source "$WORKON_HOME/initialize" |
| 108 | + """ |
| 109 | + |
| 110 | +.. warning:: |
| 111 | + |
| 112 | + Because the extension is modifying the user's working shell, care |
| 113 | + must be taken not to corrupt the environment by overwriting |
| 114 | + existing variable values unexpectedly. Avoid creating temporary |
| 115 | + variables where possible, and use unique names where necessary. |
| 116 | + Prefixing variables with the extension name is a good way to |
| 117 | + manage the namespace. For example, instead of ``temp_file`` use |
| 118 | + ``user_scripts_temp_file``. Use ``unset`` to release temporary |
| 119 | + variable names when they are no longer needed. |
| 120 | + |
| 121 | +.. warning:: |
| 122 | + |
| 123 | + virtualenvwrapper works under several shells with slightly |
| 124 | + different syntax (bash, sh, zsh, ksh). Take this portability into |
| 125 | + account when defining source hooks. Sticking to the simplest |
| 126 | + possible syntax usually avoids problems, but there may be cases |
| 127 | + where examining the ``SHELL`` environment variable to generate |
| 128 | + different syntax for each case is the only way to achieve the |
| 129 | + desired result. |
| 130 | + |
| 131 | +Registering Entry Points |
| 132 | +------------------------ |
| 133 | + |
| 134 | +The functions defined in the plugin need to be registered as *entry |
| 135 | +points* in order for virtualenvwrapper's hook loader to find them. |
| 136 | +Distribute_ entry points are configured in the ``setup.py`` for your |
| 137 | +package by mapping the entry point name to the function in the package |
| 138 | +that implements it. |
| 139 | + |
| 140 | +This partial copy of virtualenvwrapper's ``setup.py`` illustrates how |
| 141 | +the ``initialize()`` and ``initialize_source()`` entry points are |
| 142 | +configured. |
| 143 | + |
| 144 | +:: |
| 145 | + |
| 146 | + # Bootstrap installation of Distribute |
| 147 | + import distribute_setup |
| 148 | + distribute_setup.use_setuptools() |
| 149 | + |
| 150 | + from setuptools import setup |
| 151 | + |
| 152 | + setup( |
| 153 | + name = 'virtualenvwrapper', |
| 154 | + version = '2.0', |
| 155 | + |
| 156 | + description = 'Enhancements to virtualenv', |
| 157 | + |
| 158 | + # ... details omitted ... |
| 159 | + |
| 160 | + entry_points = { |
| 161 | + 'virtualenvwrapper.initialize': [ |
| 162 | + 'user_scripts = virtualenvwrapper.user_scripts:initialize', |
| 163 | + ], |
| 164 | + 'virtualenvwrapper.initialize_source': [ |
| 165 | + 'user_scripts = virtualenvwrapper.user_scripts:initialize_source', |
| 166 | + ], |
| 167 | + |
| 168 | + # ... details omitted ... |
| 169 | + }, |
| 170 | + ) |
| 171 | + |
| 172 | +The ``entry_points`` argument to ``setup()`` is a dictionary mapping |
| 173 | +the entry point *group names* to lists of entry point specifiers. A |
| 174 | +different group name is defined by virtualenvwrapper for each |
| 175 | +extension point (see :ref:`plugins-extension-points`). |
| 176 | + |
| 177 | +The entry point specifiers are strings with the syntax ``name = |
| 178 | +package.module:function``. By convention, the *name* of each entry |
| 179 | +point is the plugin name, but that is not required (the names are not |
| 180 | +used). |
| 181 | + |
| 182 | +The Hook Loader |
| 183 | +--------------- |
| 184 | + |
| 185 | +Extensions are run through a command line application implemented in |
| 186 | +``virtualenvwrapper.hook_loader``. Since ``virtualenvwrapper.sh`` is |
| 187 | +the primary caller and users do not typically need to run the app |
| 188 | +directly, no separate script is installed. Instead, to run the |
| 189 | +application, use the ``-m`` option to the interpreter:: |
| 190 | + |
| 191 | + $ python -m virtualenvwrapper.hook_loader -h |
| 192 | + Usage: virtualenvwrapper.hook_loader [options] <hook> [<arguments>] |
| 193 | + |
| 194 | + Manage hooks for virtualenvwrapper |
| 195 | + |
| 196 | + Options: |
| 197 | + -h, --help show this help message and exit |
| 198 | + -s, --source Print the shell commands to be run in the current shell |
| 199 | + -v, --verbose Show more information on the console |
| 200 | + -q, --quiet Show less information on the console |
| 201 | + |
18 | 202 | Logging
|
19 | 203 | -------
|
| 204 | + |
| 205 | +The hook loader configures logging so that messages are written to |
| 206 | +``$WORKON_HOME/hook.log``. Messages are also written to stdout, |
| 207 | +depending on the verbosity flag. The default is for messages at info |
| 208 | +or higher levels to be written to stdout, and debug or higher to go to |
| 209 | +the log file. Using logging in this way provides a convenient |
| 210 | +mechanism for users to control the verbosity of extensions. |
| 211 | + |
| 212 | +To use logging from within your extension, simply instantiate a logger |
| 213 | +and call its ``info()``, ``debug()`` and other methods with the |
| 214 | +messages. |
| 215 | + |
| 216 | +:: |
| 217 | + |
| 218 | + import logging |
| 219 | + log = logging.getLogger(__name__) |
| 220 | + |
| 221 | + def pre_mkvirtualenv(args): |
| 222 | + log.debug('pre_mkvirtualenv %s', str(args)) |
| 223 | + # ... |
| 224 | + |
| 225 | +.. _plugins-extension-points: |
| 226 | + |
| 227 | +Extension Points |
| 228 | +================ |
| 229 | + |
| 230 | +The extension point names for native plugins are different from the |
| 231 | +user customization scripts. The pattern is |
| 232 | +``virtualenvwrapper.(pre|post)_event[_source]``. The *event* is the |
| 233 | +action taken by the user or virtualenvwrapper that triggers the |
| 234 | +extension. ``(pre|post)`` is the prefix indicating whether to call |
| 235 | +the extension before or after the event. The suffix ``_source`` is |
| 236 | +added for extensions that return shell code instead of taking action |
| 237 | +directly (see :ref:`plugins-user-env`). |
| 238 | + |
| 239 | +.. _plugins-initialize: |
| 240 | + |
| 241 | +initialize |
| 242 | +---------- |
| 243 | + |
| 244 | +The ``virtualenvwrapper.initialize`` hooks are run each time |
| 245 | +``virtualenvwrapper.sh`` is loaded into the user's environment. |
| 246 | + |
| 247 | +.. _plugins-pre_mkvirtualenv: |
| 248 | + |
| 249 | +pre_mkvirtualenv |
| 250 | +---------------- |
| 251 | + |
| 252 | +The ``virtualenvwrapper.pre_mkvirtualenv`` hooks are run after the |
| 253 | +virtual environment is created, but before the new environment is |
| 254 | +activated. The current working directory for when the hook is run is |
| 255 | +``$WORKON_HOME`` and the name of the new environment is passed as an |
| 256 | +argument. |
| 257 | + |
| 258 | +.. _plugins-post_mkvirtualenv: |
| 259 | + |
| 260 | +post_mkvirtualenv |
| 261 | +----------------- |
| 262 | + |
| 263 | +The ``virtualenvwrapper.post_mkvirtualenv`` hooks are run after a new |
| 264 | +virtual environment is created and activated. ``$VIRTUAL_ENV`` is set |
| 265 | +to point to the new environment. |
| 266 | + |
| 267 | +.. _plugins-pre_activate: |
| 268 | + |
| 269 | +pre_activate |
| 270 | +------------ |
| 271 | + |
| 272 | +The ``virtualenvwrapper.pre_activate`` hooks are run just before an |
| 273 | +environment is enabled. The environment name is passed as the first |
| 274 | +argument. |
| 275 | + |
| 276 | +.. _plugins-post_activate: |
| 277 | + |
| 278 | +post_activate |
| 279 | +------------- |
| 280 | + |
| 281 | +The ``virtualenvwrapper.post_activate`` hooks are run just after an |
| 282 | +environment is enabled. ``$VIRTUAL_ENV`` is set to point to the |
| 283 | +current environment. |
| 284 | + |
| 285 | +.. _plugins-pre_deactivate: |
| 286 | + |
| 287 | +pre_deactivate |
| 288 | +-------------- |
| 289 | + |
| 290 | +The ``virtualenvwrapper.pre_deactivate`` hooks are run just before an |
| 291 | +environment is disabled. ``$VIRTUAL_ENV`` is set to point to the |
| 292 | +current environment. |
| 293 | + |
| 294 | +.. _plugins-post_deactivate: |
| 295 | + |
| 296 | +post_deactivate |
| 297 | +--------------- |
| 298 | + |
| 299 | +The ``virtualenvwrapper.post_deactivate`` hooks are run just after an |
| 300 | +environment is disabled. The name of the environment just deactivated |
| 301 | +is passed as the first argument. |
| 302 | + |
| 303 | +.. _plugins-pre_rmvirtualenv: |
| 304 | + |
| 305 | +pre_rmvirtualenv |
| 306 | +---------------- |
| 307 | + |
| 308 | +The ``virtualenvwrapper.pre_rmvirtualenv`` hooks are run just before |
| 309 | +an environment is deleted. The name of the environment being deleted |
| 310 | +is passed as the first argument. |
| 311 | + |
| 312 | +.. _plugins-post_rmvirtualenv: |
| 313 | + |
| 314 | +post_rmvirtualenv |
| 315 | +----------------- |
| 316 | + |
| 317 | +The ``virtualenvwrapper.post_rmvirtualenv`` hooks are run just after |
| 318 | +an environment is deleted. The name of the environment being deleted |
| 319 | +is passed as the first argument. |
| 320 | + |
| 321 | +.. _Distribute: http://packages.python.org/distribute/ |
0 commit comments