!!! abstract “”
Compose multiple apps into a single pipeline using the `+` operator, see what happens when types don't match, and observe how `NotCompleted` propagates through a pipeline without raising exceptions.
Consider an app that performs a molecular evolutionary analysis
(fit_model) and another that extracts statistics from the
result (extract_stats). You could apply them
sequentially:
python { notest } fitted = fit_model(alignment) stats = extract_stats(fitted)
Composability simplifies this into a single callable:
python { notest } app = fit_model + extract_stats stats = app(alignment)
You can have many more apps in a composed function than just two.
We compose three apps: a loader, a processor, and a writer.
```python { notest } from cogent3 import get_app from scinexus import open_data_store
out_dstore = open_data_store(path_to_dir, suffix=“fa”, mode=“w”)
loader = get_app(“load_aligned”, format_name=“fasta”, moltype=“dna”) cpos3 = get_app(“take_codon_positions”, 3) writer = get_app(“write_seqs”, out_dstore, format_name=“fasta”)
### Using apps sequentially
```python { notest }
data = loader("data/primate_brca1.fasta")
just3rd = cpos3(data)
m = writer(just3rd)
python { notest } process = loader + cpos3 + writer m = process("data/primate_brca1.fasta")
The result is identical, but the composed form is more concise and
enables batch processing via apply_to().
Loaders and writers are special cases. If included, a loader must always be first:
python { notest } app = a_loader + a_generic
If included, a writer must always be last:
python { notest } app = a_generic + a_writer
Changing the order for either will raise a
TypeError.
Apps define the type of input they accept and the type of output they
produce. For two apps to be composed, the output type of the app on the
left must overlap with the input type of the app on the right. If they
don’t match, a TypeError is raised.
NotCompleted
propagationIf any step in a composed pipeline returns a
NotCompleted, subsequent steps are skipped and the
NotCompleted is returned as the final result.
???+ example “Condition not satisfied”
```python { linenums="1" notest }
from cogent3 import get_app
reader = get_app("load_aligned", format_name="fasta")
select_seqs = get_app("take_named_seqs", "Mouse", "Human")
app = reader + select_seqs
result = app("data/primate_brca1.fasta")
print(result) # (1)!
# NotCompleted(type=FAIL, origin=take_named_seqs, source="primate_brca1",
# message="named seq(s) {'Mouse'} not in ('FlyingLem', 'TreeShrew', 'Galago',
# 'HowlerMon', 'Rhesus', 'Orangutan', 'Gorilla', 'Chimpanzee', 'Human')")
```
1. A successful load but a failed selection — the `NotCompleted` from `select_seqs` is returned
???+ example “Caught an exception”
```python { linenums="1" notest }
from cogent3 import get_app
reader = get_app("load_aligned", format_name="fasta")
select_seqs = get_app("take_named_seqs", "Mouse", "Human")
app = reader + select_seqs
result = app("primate_brca1.fasta")
print(result) # (1)!
# NotCompleted(type=ERROR, origin=load_aligned, source="primate_brca1",
# message="Traceback (most recent call last): File
# "/Users/gavin/repos/SciNexus/src/scinexus/composable.py", line 545, in __call__
# result = self.main(val, *args, **kwargs)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File
# "/Users/gavin/repos/SciNexus/.venv/lib/python3.12/site-
# packages/cogent3/app/io.py", line 334, in main return _load_seqs(path,
# cogent3.make_aligned_seqs, self._parser, self.moltype)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File
# "/Users/gavin/repos/SciNexus/.venv/lib/python3.12/site- [...]
```
1. An error during load — `select_seqs` is never called.