Back to Claude Scientific Skills

Migrating ETE 3 Code to ETE 4

skills/etetoolkit/references/migration-ete3-to-ete4.md

2.55.011.5 KB
Original Source

Migrating ETE 3 Code to ETE 4

ETE 4 is a breaking API revision, not an import-only upgrade. This guide targets ETE 4.4.0 and summarizes the official migration guide plus behavior verified against the installed release.

Release Baseline

  • ETE 4.0.0 and 4.1.1 were released March 28, 2025.
  • ETE 4.1.1 marked ETE 4 as out of beta and available on PyPI.
  • ETE 4.4.0 was released September 3, 2025.
  • Package name and primary import are ete4.

Install side by side only when a legacy project genuinely requires ETE 3:

bash
uv pip install "ete4==4.4.0"

Do not leave both APIs implicit in one code path. Name compatibility boundaries and test them separately.

Import Changes

ETE 3:

python
from ete3 import NCBITaxa, PhyloTree, Tree

ETE 4:

python
from ete4 import GTDBTaxa, NCBITaxa, PhyloTree, Tree

ETE 3 exposed equivalent TreeNode and Tree classes. ETE 4 uses Tree.

Qt visualization imports moved:

python
# ETE 3
from ete3 import NodeStyle, TextFace, TreeStyle

# ETE 4
from ete4.treeview import NodeStyle, TextFace, TreeStyle

Current web visualization uses:

python
from ete4.smartview import Layout, PropFace, TextFace

SmartView and treeview TextFace classes are different types.

Construction and File Input

File path ambiguity was removed

ETE 3:

python
tree = Tree("tree.nw", format=1)

ETE 4:

python
from pathlib import Path

with Path("tree.nw").open(encoding="utf-8") as handle:
    tree = Tree(handle, parser=1)

Use strings for Newick text and pass an open file object for file input. ETE 4.4.0 still contains a path-like string heuristic internally, but relying on it conflicts with the documented contract and makes input behavior ambiguous.

New nodes with properties

ETE 3:

python
tree = Tree(name="root", dist=0, support=1)

ETE 4:

python
tree = Tree({"name": "root", "dist": 0, "support": 1})

ETE 4 accepts arbitrary initial properties through the dictionary.

Property Model

ETE 3 required name, distance, and support defaults. In ETE 4 these properties can be absent, and their convenience accessors can return None.

ETE 3:

python
node.add_feature("habitat", "marine")
node.add_features(group="case", score=0.8)
print(node.features)

ETE 4:

python
node.add_prop("habitat", "marine")
node.add_props(group="case", score=0.8)
print(node.props)

General argument renames:

  • feature / featuresprop / props
  • attribute / attributesprop / props
  • property / propertiesprop / props

Replace hasattr(node, "x") tests for custom metadata with:

python
if "x" in node.props:
    value = node.props["x"]

Lookup, Predicates, and Relatives

ETE 3ETE 4
tree & "A"tree["A"]
tree.get_tree_root()tree.root
node.is_leaf()node.is_leaf
node.is_root()node.is_root
tree.get_common_ancestor(a, b)tree.common_ancestor(a, b)
node.get_ancestors()node.ancestors()
tree.get_leaves_by_name("A")tree.search_leaves_by_name("A")

ETE 4 also supports positional IDs:

python
node = tree[0, 1, 0]
print(node.id, node.level)

Name lookup returns the first match in both practical patterns. Validate uniqueness when names are identifiers.

Iterator Renames

ETE 3ETE 4
get_leaves() / iter_leaves()leaves()
get_descendants() / iter_descendants()descendants()
get_edges() / iter_edges()edges()
get_leaf_names()leaf_names()
get_ancestors()ancestors()

ETE 4 returns iterators:

python
leaves = list(tree.leaves())
names = list(tree.leaf_names())

Do not call len(tree.leaves()) or index the result without first creating a list.

Text and Newick I/O

Parser rename

ETE 3:

python
tree = Tree(newick, format=1)
newick = tree.write(format=1)

ETE 4:

python
tree = Tree(newick, parser=1)
newick = tree.write(parser=1)

Named parser aliases include "name" and "support".

ASCII rename

ETE 3:

python
print(tree.get_ascii(show_internal=True))

ETE 4:

python
print(tree.to_str(show_internal=True, props=["name", "dist"]))

Extended-property semantics

ETE 3 features=[] meant all available features. In ETE 4:

python
tree.write(props=[])                  # no extended properties
tree.write(props=["species", "host"]) # selected properties
tree.write(props=None)                # all available properties

This reversal is important. Use an explicit selected list for external output.

Use keyword arguments with write(). Its first positional argument is outfile in ETE 4, not the ETE 3 feature selection.

Custom formatters

ETE 3:

python
newick = tree.write(
    format=1,
    dist_formatter="%0.1f",
    name_formatter="TEST-%s",
)

ETE 4:

python
from ete4.parser import newick

parser = newick.make_parser(
    1,
    dist="%0.1f",
    name="TEST-%s",
)
text = tree.write(parser=parser)

Distances and Topology

ETE 3ETE 4
A.get_distance(B)tree.get_distance(A, B)
topology_only=Truetopological=True
convert_to_ultrametric()to_ultrametric()
resolve_polytomy(recursive=True)resolve_polytomy(descendants=True)

ETE 4 adds a direct midpoint convenience:

python
tree.set_midpoint_outgroup()

The older two-step pattern remains valid:

python
midpoint = tree.get_midpoint_outgroup()
tree.set_outgroup(midpoint)

ETE 4.4.0 adds distance_matrix(), which supersedes cophenetic_matrix() for new code.

Random Tree Generation

ETE 3:

python
tree.populate(
    size,
    names_library=names,
    random_branches=True,
    dist_range=(0, 1),
)

ETE 4:

python
import random

tree.populate(
    size,
    names=names,
    model="yule",
    dist_fn=random.random,
    support_fn=lambda: 1,
)

Set the random seed when generated topology or distances must be reproducible.

Robinson-Foulds Unpacking

ETE 4.4.0 returns seven values:

python
(
    rf,
    max_rf,
    common,
    edges_self,
    edges_other,
    discarded_self,
    discarded_other,
) = tree.robinson_foulds(other)

ETE 3 examples that unpack only five values must be updated.

Argument names also use prop_t1 and prop_t2 rather than feature-oriented names.

PhyloTree Changes and Traps

The central ETE 3 methods remain, but use ETE 4 property and iterator syntax:

python
from ete4 import PhyloTree

tree = PhyloTree(
    "((Hsa|g1,Ptr|g1),Mmu|g1);",
    sp_naming_function=lambda name: name.split("|", 1)[0],
)

events = tree.get_descendant_evol_events(sos_thr=0.0)
for leaf in tree.leaves():
    print(leaf.name, leaf.species)

Pass sp_naming_function explicitly for species-aware methods. The current source defaults it to None, despite older documentation describing an automatic first-three-character rule. Species-overlap event detection also requires a rooted, fully bifurcating gene tree.

Do not pass a species tree to get_descendant_evol_events(). In ETE 4.4.0 its signature accepts only sos_thr. Use reconciliation:

python
reconciled_tree, events = gene_tree.reconcile(species_tree)

After event detection, inspect:

python
node.props.get("evoltype")

rather than relying on ETE 3 feature helpers.

Taxonomy Changes

ETE 3 examples commonly refer to:

text
~/.etetoolkit/taxa.sqlite

ETE 4 stores taxonomy data under:

text
~/.local/share/ete/

The current documentation's approximately 600 MB NCBI and 72 MB GTDB figures are better treated as local first-use footprint estimates, not compressed network download sizes. Archive sizes vary by release and can be much smaller; allow extra space for parsed SQLite and temporary conversion files.

ETE 4 adds first-class GTDB support:

python
from ete4 import GTDBTaxa

NCBI numeric TaxIDs and GTDB string identifiers are not interchangeable.

Visualization Migration

Preferred ETE 4 SmartView

python
from ete4 import Tree

tree = Tree("((A,B),C);")
tree.explore()
tree.render_sm("tree.png")

Custom SmartView:

python
from ete4.smartview import Layout, PropFace


def draw_node(node):
    if node.is_leaf:
        return PropFace("name", position="right")


layout = Layout("labels", draw_node=draw_node)
tree.explore(layouts=[layout])

SmartView style dictionaries and faces are not compatible with TreeStyle or NodeStyle.

Retained Qt treeview

ETE 3:

python
from ete3 import NodeStyle, TreeStyle

ETE 4:

python
from ete4.treeview import NodeStyle, TreeStyle

Install:

bash
uv pip install "ete4[treeview]==4.4.0"

Qt treeview remains the option for vector PDF/SVG. SmartView's render_sm() in ETE 4.4.0 creates PNG screenshot data.

Clustering

ETE 3:

python
from ete3 import ClusterTree

ETE 4.4.0:

text
ImportError: cannot import name 'ClusterTree' from 'ete4'

Do not document ClusterTree, linked matrix profiles, silhouette, or Dunn methods as ETE 4 capabilities. Use a maintained clustering library for those calculations and a normal ETE Tree for topology display.

Command-Line Caveat

The ete4 compare command shipped in ETE 4.4.0 still calls Tree(..., format=...) internally and fails with the removed keyword. Use Tree.robinson_foulds(), Tree.compare() for unique labels, or this skill's scripts/tree_operations.py compare helper. Avoid the duplication-aware Tree.compare(has_duplications=True) path as well; upstream source labels that branch as likely broken.

Porting Example

ETE 3:

python
from ete3 import Tree

tree = Tree("tree.nw", format=1)
node = tree & "A"
node.add_feature("group", "case")

for leaf in tree.iter_leaves():
    if leaf.is_leaf():
        print(leaf.name)

tree.write(
    outfile="out.nhx",
    format=1,
    features=["group"],
)

ETE 4:

python
from pathlib import Path

from ete4 import Tree

with Path("tree.nw").open(encoding="utf-8") as handle:
    tree = Tree(handle, parser=1)

node = tree["A"]
node.add_prop("group", "case")

for leaf in tree.leaves():
    if leaf.is_leaf:
        print(leaf.name)

tree.write(
    outfile="out.nhx",
    parser=1,
    props=["group"],
)

Mechanical Porting Checklist

Search legacy code for:

text
from ete3
TreeNode
format=
features=
feature=
attributes=
attribute=
add_feature
add_features
.features
get_ascii
get_tree_root
get_common_ancestor
get_leaves
iter_leaves
get_descendants
iter_descendants
get_leaf_names
get_leaves_by_name
convert_to_ultrametric
topology_only
is_leaf()
is_root()
 & "
ClusterTree
TreeStyle
NodeStyle

Then:

  1. Replace each symbol using this guide.
  2. Review every Newick read/write parser.
  3. Convert iterator consumers deliberately.
  4. Validate property export semantics.
  5. Separate SmartView and treeview layouts.
  6. Remove or redesign ClusterTree workflows.
  7. Test representative trees with names, support, branch lengths, NHX properties, duplicate tips, and polytomies.
  8. Compare scientific outputs, not just successful execution.

Verification Snippet

python
import ete4
from ete4 import Tree

assert ete4.__version__ == "4.4.0"

tree = Tree("((A:1,B:1)95:0.2,C:1);", parser="support")
assert list(tree.leaf_names()) == ["A", "B", "C"]
assert tree["A"].is_leaf

round_trip = tree.write(parser="support", props=[])
assert round_trip == "((A:1,B:1)95:0.2,C:1);"

Upstream References