Skip to main content

Sharing Common Logic

It is common to want to share common logic between your scripts. This can be done easily using relative imports in both Python and TypeScript.

Note that in both the webeditor and with the CLI, your scripts do not necessarily need to have a main function. If they don't, they are assumed to be shared logic and not runnable scripts.

It works extremely well in combination with Developing scripts locally and you can easily sync your scripts with the CLI.

Python relative imports for sharing common logic

It is possible to import directly from other Python scripts. One can simply follow the path layout. For instance, import foo from f.<foldername>.script_name. A more complete example below:

# u/user/common_logic

def foo():
print('Common logic!')

And in another Script:

# u/user/custom_script

from u.user.common_logic import foo

def main():
return foo()

It works with Scripts contained in folders, and for scripts contained in user-spaces, e.g: f.<foldername>.script_path or u.<username>.script_path.

You can also do relative imports to the current script. For instance.

# if common_logic is a script in the same folder or user-space
from .common_logic import foo
# otherwise if you need to access the folder 'folder'
from ..folder.common_logic import foo

Beware that you can only import scripts that you have view rights on at time of execution.

The folder layout is identical with the one that works with the CLI for syncing scripts locally and on Windmill. See Developing scripts locally.

Deno or Bun relative imports for sharing common logic

Similarly to Python, it is possible to import directly from other TypeScript scripts. One can simply follow the path layout. For instance, import { foo } from "/f/<foldername>/script_name.ts". A more verbose example below:

import { main as foo, util } from '../my_script_path.ts';

Relative imports syntax is much preferred as it will work on local editors without further configuration.

You may also use absolute imports:

import { main as foo, util } from '/f/common/my_script_path.ts';

export async function main() {
await foo();
util();
}

but to make it work with your local editor, you will need the following configuration in tsconfig.json

{
"compilerOptions": {
"paths": {
"/*": ["./*"]
}
}
}

Note that path in Windmill can have as many depth as needed, so you can have paths like this f/folder/subfolder/my_script_path.ts and relative imports will work at any level. Hence, it will work exactly the same as on local.

Bundle per Script built by CLI

This method can only be deployed from the CLI, on local development.

To work with large custom codebases, there is another mode of deployment that relies on the same mechanism as similar services like Lambda or cloud functions: a bundle is built locally by the CLI using esbuild and deployed to Windmill.

This bundle contains all the code and dependencies needed to run the script.

Windmill CLI, it is done automatically on wmill sync push for any script that falls in the patterns of includes and excludes as defined by the wmill.yaml (in the codebase field).

Bash logic sharing

You can reuse bash scripts by fetching them "raw" and source them. The url is as follows:

curl -H "Authorization: Bearer <Token>" <INSTANCE>/api/w/<workspace>/scripts/raw/p/<path>.sh

Tracking Relative Imports on Local Development

On local development, Windmill automatically tracks relative imports in Bun and Python such that if you update a common dependency and update its imports, it will now re-trigger deployment and lockfile computation of all the scripts that depend on it (it was working for Python but not Bun before).

When doing wmill sync pull, the wmill-lock.yaml will now automatically be updated, avoiding re-triggering lockfile computation for all files, only the ones that have changed from last sync.

Windmill can also track such imports in inline scripts of flows and will surgically update the inline lockfiles of those flows if the relative imports change.