Per Project Module Search Paths?

This is a question about the Script Editor’s > Tools > Options > Python > Module Search Paths, in both Rhino and Grasshopper it appears to be an application level setting. On the Rhino side this makes sense, but on the Grasshopper side I would expect this to be a setting that is saved on a per GH file basis so that external code can be referenced per GH project.

Does anyone have a suggested approach that allows including your external python modules in a way that doesn’t bleed over into other GH projects, but is still well supported by the script editor?

Definitely curious on this as well! Thanks for posting

1 Like

Ok, I found a solution for handling per project external dependencies with the existing script component! I’d be very interested if other people have cleaner solutions to this.

This approach is based on this thread

Where “Path Script” is:

import os
scriptPath = os.path.join("..", "gh_script_inc", "externalScript1.py")

and “External Script” is in a file on the file system as:

x: int
result: float
result = x * 1.5
print(f"{result=}")

I’ve also verified that the external script can use this notation for installing dependencies, like so:

#! python3
#r: pandas

While this does achieve my goal, I’d be curious about other people’s improvements on this. Is there a better way to refresh the external code? Is there a better system independent way of creating the path to the external script?

2 Likes

Nice! Here’s what I’ve been doing but I’m new to all this module business so I have no idea on the pros/cons of different options.

import sys

# Add the directory containing modules to the Python path
modules_path = r"mysupercoolpathtoallmypythonmodules"
sys.path.append(modules_path)

from SomeModule import SomeFunction
from SomeOtherModule import AnEvenCoolerFunction

I put this under my imports at the top of the file and then call the module functions as needed in my main scripts.

I would love a Python Wiz to chime in on all this :snake:

Thanks for sharing @Jonathan_Garrison !

1 Like

One can use reload(moduleName) to update the imported code when the source is externally modified.

Don’t know about the wiz part, but one should check if the new path is already in sys.path before appending it. Else one might end up appending it each time the script runs (at least in GhPython):

1 Like

I’m not sure if this helps, but if a Grasshopper file is saved, it’s file path will automatically be added to the GhPython path as well. Meaning that if you place a Python module in the same folder as the Grasshopper file, you should be able to just import it in GhPython without any path fiddling:

An unsaved Grasshopper file:

A saved Grasshopper file:

I’m not sure if this feature/behaviour has been added to the new Grasshopper script editor yet. Edit: Just checked, it does not (ping @eirannejad):


2 Likes

Thank you so much for the detailed descriptions of the different sys.paths loaded in different scenarios. I’ve been experimenting with the available module inclusion methods and here is some of the behavior that I’ve found. All of these experiments were done in the Rhino 8 (8.9.24194.18121) Grasshopper Python3 script editor:

  1. Using env directive with relative paths in a Python 3 component:
    # env .
    # env …/gh_script_inc

Pros:

  • Works for including external code with a path relative to the gh file. This is almost the holy grail.

Cons:

  • loaded code cannot be reloaded via “# flag: python.reloadEngine” nor through importlib.reload (which gives a “spec not found error”. This limitation rules this technique out for actual dev usage.
  1. Using Python 3 Script Input Parameter (Direct script file loading by GH Component, see my post above)
    (directions: Shift+RightClick Python 3 Component > choose “Script Input Parameter”)

Pros:

  • All auto-generated component input variables are directly available in the external script
  • Access to the component itself (inputs, outputs, etc) in script is possible
  • All component code lives in one file and code is identical to code that would have been in the gh script editor

Cons:

  • No script “play” button. During dev the script has to be re-run by invalidating an input on the script component (see my solution above)
  • Looks clunky to the non-coders working with the GH file, because path to actual script needs to be abstracted into an external python component so that it can generate a script link relative to the GH file location. I think this awkardness in the GH file is problematic.
  1. Normal GH Python 3 component with manual “sys.path” additions and “import” module statements

Pros:

  • Can be run with script “Play” button
  • importlib.reload can be used to reload the modules during dev
  • looks clean when looking at the GH file

Cons:

  • Requires repetitive boiler plate in each Python 3 component to append relative path module directories and then import the target modules which can be reloaded with importlib.reload
  1. Script Editor > Tools Menu > Options > Python 3 > Module Search Path setting

Pros:

  • Universal Grasshopper setting works across all components at once. Yay!

Cons:

  • Is a setting at the Grasshopper level and does not get saved with the GH file. This means all your projects must use the same module search paths and complicates sharing your gh file with other users on other systems. Bummer.

It’s interesting thinking about this problem. Rhino/Grasshopper/Python is such a powerful combination.

Does anyone have any additional external code inclusion techniques to try out?