0

I have a shell function (ok, it's the deactivate() function defined by a Python virtualenv), and I would like to patch it by inserting some statements before or after it's executed. My goal is to have a new deactivate() function that does what the old one did, plus a bit more. Is there a way to accomplish this?

The original function is dynamically defined from various places (the various virtualenvs), so modifying it at every source is not an attractive option.

  • what about writing new function and calling deactivate from it? it should word in every venv you have – mrc02_kr Oct 11 '18 at 10:50
  • That would work, but I would prefer to be able to type "deactivate" like I'm used to. The point of the question is not to have to change the name. A solution that copies the current definition to a new name would be fine. – alexis Oct 11 '18 at 10:57
  • (Plus, in general, there may be other scripts that call the original name, and we want its behavior to be patched.) – alexis Oct 11 '18 at 11:00
1

You either could ceate a deactivate alias (if that is enough) which has higher priority than a function. In that you could run a command before, but is not easy to run after.

alias deactivate='before; deactivate'

Or simply (much better) create a function with a simpler name:

deact(){ [[ $somevar == true ]] && deactivate
         [[ $othervar == admin ]] && sudo deactivate
       }

Or anything you want to implement. Yes that will only run where defined. It is **not a replacement for the word deactivate nor it works for other users.

The only way to do that is to create an executable program called deactivate, but that has lower priority than aliases and functions. Both will need to be un-defined.

  • Thanks for the suggestions. In this case, an alias will work pretty well, also for post-processing, because the function deactivate does not take any arguments. But since deactivate manipulates the environment and is different for each virtualenv, an executable is not going to cut it. (Neither is a script combined with an alias for sourcing, as far as I can see.) – alexis Oct 11 '18 at 12:19
1

If your shell is bash,zsh or ksh93 you can take advantage of the typeset -f builtin, which shows the source of a function:

# usage patch_func funcname before after
function patch_func {
        typeset nl=$'\n' lb={ src=`typeset -f "$1"` before=$2 after=$3
        src="${src/$lb/$lb$nl    $before$nl}"
        src="${src%\}*}$nl    $after$nl}"
        eval "$src"
}

Example:

$ deactivate(){ echo -n "{$0}"; for i; do echo -n " {$i}"; done; echo; }
$ patch_func deactivate 'echo before' 'echo after'
$ typeset -f deactivate
deactivate ()
{
    echo before;
    echo -n "{$0}";
    for i in "$@";
    do
        echo -n " {$i}";
    done;
    echo;
    echo after
}

This is of course quite fragile.

0

Although this answer does not directly apply to virtualenv usage - how about using virtualenvwrapper? It's basically virtualenv on steroids, taking care of envs management. Once installed, you get a set of additional commands:

  • workon lists all available environments when called without arguments, and activates the env when called with env name as agument, for example workon myenv activates the env myenv;
  • mkvirtualenv myenv creates a new env myenv;
  • rmvirtualenv myenv removes it.

But there's more - virtualenvwrapper defines a set of useful hooks where you can implement your own logic for envs customization, for example:

  • premkvirtualenv is executed when an env is created, but not activated yet;
  • postmkvirtualenv is executed when an env is created and activated;
  • preactivate is executed when an env activation was triggered;
  • postactivate is executed when an env was activated;
  • predeactivate is executed when an env deactivation was triggered;
  • postdeactivate is executed when an env was deactivated;

and many more. For example, I have a postmkvirtualenv hook that installs ipython in a new env:

$ cat $VIRTUALENVWRAPPER_HOOK_DIR/postmkvirtualenv
#!/bin/bash
# This hook is sourced after a new virtualenv is activated.

env=$(basename "$VIRTUAL_ENV")
logger -s -t "($env)" "installing ipython ..."
"$VIRTUAL_ENV/bin/pip" install ipython --quiet

To define additional logic for the env deactivation, you would thus have to write a custom bash script and place its contents to either $VIRTUALENVWRAPPER_HOOK_DIR/predeactivate (if you need to run your stuff while the env is still activated) or $VIRTUALENVWRAPPER_HOOK_DIR/postdeactivate (if you need the env to be deactivated to run the custom code).

For reference: virtualenvwrapper docs.

For a complete list of hooks, refer to Per-User Customization.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.