Skip to content

Commit 4b91429

Browse files
committed
update extension entry point docs
1 parent a257f73 commit 4b91429

File tree

3 files changed

+331
-45
lines changed

3 files changed

+331
-45
lines changed

docs/source/developers.rst

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,3 @@ The test suite for virtualenvwrapper uses `shunit2
6363
<http://shunit2.googlecode.com/>`_. To run the tests under bash, sh,
6464
and zsh, use ``make test``. To add new tests, modify or create an
6565
appropriate script in the ``tests`` directory.
66-
67-
.. _developers-extensions:
68-
69-
Creating Extension Plugins
70-
==========================
71-
72-
virtualenvwrapper adds several hook points you can use to modify its
73-
behavior. End-users can provide simple shell scripts (see
74-
:ref:`hook-scripts`). Extensions can also be implemented in Python by
75-
using the ``setuptools`` style *entry points*.
76-
77-
Types of Hooks
78-
--------------
79-
80-
Existing Hook Points
81-
--------------------
82-

docs/source/plugins.rst

Lines changed: 309 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,318 @@
44
Extension Plugins
55
===================
66

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.
1112

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__)
1442

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
1668
--------------------
1769

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+
18202
Logging
19203
-------
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

Comments
 (0)