Skip to Content
NodebarPython Nodes

Python Nodes

Python is the most common language for nodes in Bioinformatics Studio. Here’s how they work — no PhD required.


The Simplest Python Node

Here’s a complete, working Python node:

@node() def normalize(adata, target_sum=1e4): import scanpy as sc sc.pp.normalize_total(adata, target_sum=target_sum) return adata

This node has 2 inputs (adata, target_sum) and 1 output (adata).


The @node Decorator

Every Python node starts with a decorator — a small tag that tells the system “this is a workflow node, not just a regular function.”

@node() def my_node(): ...

Add @node() above your function, and the system knows what to do.


What Is an Input?

Inputs are the function parameters — whatever goes inside the parentheses.

@node() def normalize(adata, target_sum): # ↑ ↑ # input 1 input 2

This node has two inputs: adata and target_sum. When the workflow runs, the system automatically injects values from connected upstream nodes into these parameters. You don’t do anything — just name them, and the values appear.

Default Values

You can provide default values for inputs. If nothing is connected, the default is used:

@node() def normalize(adata, target_sum=1e4): # ↑ default value

What Is an Output?

Outputs come from the return statement at the end of your function.

@node() def normalize(adata, target_sum): sc.pp.normalize_total(adata, target_sum=target_sum) return adata # ↑ # output 1

Multiple Outputs

Return multiple values separated by commas:

@node() def qc_filter(adata, min_genes): ... return adata, qc_report # ↑ ↑ # out 1 out 2

Now downstream nodes can connect to either adata or qc_report.


The Golden Rule

Always return a variable name, never an expression.

✅ Do this:

result = a + b return result

❌ Not this:

return a + b

The system reads the variable name to create the output handle. return a + b has no name — it can’t create a proper handle.


How Variables Arrive

When you connect Node A’s output to Node B’s input, the value flows automatically. Inside your function, the parameter is just… there.

@node() def my_node(adata): # adata already contains the AnnData object from the upstream node print(adata.shape) return adata

No loading, no deserialization, no input['adata'] — just use it directly.


A Complete Example

Here’s a real quality control node for single-cell data:

@node() def run_qc(adata, min_genes=200, max_mt_percent=20): import scanpy as sc adata.var['mt'] = adata.var_names.str.startswith('MT-') sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], inplace=True) adata = adata[adata.obs.n_genes_by_counts >= min_genes, :] adata = adata[adata.obs.pct_counts_mt < max_mt_percent, :] return adata

Real-Time Output

Want to see things while your node runs? Use display():

@node() def analyze(adata): display(adata.obs.head()) # shows a table in real-time ...

And plt.show() is automatically captured — just call it and figures appear in the output panel:

@node() def plot_data(adata): import matplotlib.pyplot as plt plt.scatter(adata.obs['n_genes'], adata.obs['total_counts']) plt.show() # captured automatically — no need to save to file return adata

Plain Python Scripts (No Decorator)

You can also write Python without the @node() decorator — just a plain script. In this case, UPPERCASE variable assignments at the top become inputs (same as bash and R nodes):

INPUT_FILE = "data/counts.csv" OUTPUT_DIR = "results/" import pandas as pd df = pd.read_csv(INPUT_FILE) df.to_csv(f"{OUTPUT_DIR}/processed.csv", index=False) result = df

This script has 2 inputs (INPUT_FILE, OUTPUT_DIR) and 1 output (result — the last assigned variable).

Use the @node() decorator when you want clean function-based nodes. Use plain scripts for quick, simple tasks.


Good to Know

Verbose output? Write to a log file

If your code produces a lot of output (e.g., training a model), write logs to a file and only print key milestones:

@node() def train_model(adata): import logging logging.basicConfig(filename='training.log', level=logging.INFO) logging.info('Started training') # ... training loop ... print('Training complete. See training.log for details.') return model

No timeout — nodes can run for hours

There’s no execution timeout. If your node runs a long alignment or training job, that’s fine — it won’t be killed.

Large objects are handled automatically

DataFrames, AnnData objects, and other non-JSON-serializable objects are automatically serialized to disk and passed by reference. You don’t need to manually save and reload — just return them.


Quick Summary

ConceptHow it works
Decorator@node() above your function
InputsFunction parameters
Default valuesdef my_node(adata, threshold=0.05):
Outputsreturn values
Multiple outputsreturn a, b, c
Golden ruleReturn variable names, not expressions
Variable arrivalAutomatic — just use the parameter name
Real-time displaydisplay(value) or plt.show()
Plain scriptsUPPERCASE assignments become inputs (no decorator needed)
Verbose outputWrite to a log file, print only milestones
No timeoutNodes can run for hours or days
Last updated on