migration-ete3-to-ete4.md
576 lines · skills/etetoolkit/references/migration-ete3-to-ete4.md at 5f2bc03
Most marks come from matching words in the file, so treat them as pointers.
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.
Raw lines 1 to 6Release 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:
uv pip install "ete4==4.4.0"Do not leave both APIs implicit in one code path. Name compatibility boundaries and test them separately.
Raw lines 7 to 22Import Changes
ETE 3:
from ete3 import NCBITaxa, PhyloTree, TreeETE 4:
from ete4 import GTDBTaxa, NCBITaxa, PhyloTree, TreeETE 3 exposed equivalent TreeNode and Tree classes. ETE 4 uses Tree.
Qt visualization imports moved:
# ETE 3
from ete3 import NodeStyle, TextFace, TreeStyle
# ETE 4
from ete4.treeview import NodeStyle, TextFace, TreeStyleCurrent web visualization uses:
from ete4.smartview import Layout, PropFace, TextFaceSmartView and treeview TextFace classes are different types.
Construction and File Input
Raw lines 57 to 58File path ambiguity was removed
ETE 3:
tree = Tree("tree.nw", format=1)ETE 4:
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.
Raw lines 59 to 79New nodes with properties
ETE 3:
tree = Tree(name="root", dist=0, support=1)ETE 4:
tree = Tree({"name": "root", "dist": 0, "support": 1})ETE 4 accepts arbitrary initial properties through the dictionary.
Raw lines 80 to 95Property 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:
node.add_feature("habitat", "marine")
node.add_features(group="case", score=0.8)
print(node.features)ETE 4:
node.add_prop("habitat", "marine")
node.add_props(group="case", score=0.8)
print(node.props)General argument renames:
feature/features→prop/propsattribute/attributes→prop/propsproperty/properties→prop/props
Replace hasattr(node, "x") tests for custom metadata with:
if "x" in node.props:
value = node.props["x"]Raw lines 96 to 129Lookup, Predicates, and Relatives
| ETE 3 | ETE 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:
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.
Raw lines 130 to 151Iterator Renames
| ETE 3 | ETE 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:
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
Raw lines 172 to 173Parser rename
ETE 3:
tree = Tree(newick, format=1)
newick = tree.write(format=1)ETE 4:
tree = Tree(newick, parser=1)
newick = tree.write(parser=1)Named parser aliases include "name" and "support".
ASCII rename
ETE 3:
print(tree.get_ascii(show_internal=True))ETE 4:
print(tree.to_str(show_internal=True, props=["name", "dist"]))Raw lines 192 to 205Extended-property semantics
ETE 3 features=[] meant all available features. In ETE 4:
tree.write(props=[]) # no extended properties
tree.write(props=["species", "host"]) # selected properties
tree.write(props=None) # all available propertiesThis 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:
newick = tree.write(
format=1,
dist_formatter="%0.1f",
name_formatter="TEST-%s",
)ETE 4:
from ete4.parser import newick
parser = newick.make_parser(
1,
dist="%0.1f",
name="TEST-%s",
)
text = tree.write(parser=parser)Raw lines 221 to 245Distances and Topology
| ETE 3 | ETE 4 |
|---|---|
A.get_distance(B) | tree.get_distance(A, B) |
topology_only=True | topological=True |
convert_to_ultrametric() | to_ultrametric() |
resolve_polytomy(recursive=True) | resolve_polytomy(descendants=True) |
ETE 4 adds a direct midpoint convenience:
tree.set_midpoint_outgroup()get_midpoint_outgroup() identifies the edge containing the midpoint, but set_outgroup() defaults to a different cut position on that edge. Use set_midpoint_outgroup() when exact placement is required.
ETE 4.4.0 adds distance_matrix(), which supersedes cophenetic_matrix() for new code.
Random Tree Generation
ETE 3:
tree.populate(
size,
names_library=names,
random_branches=True,
dist_range=(0, 1),
)ETE 4:
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.
Raw lines 268 to 296Robinson-Foulds Unpacking
ETE 4.4.0 returns seven values:
(
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:
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:
reconciled_tree, events = gene_tree.reconcile(species_tree)After event detection, inspect:
node.props.get("evoltype")rather than relying on ETE 3 feature helpers.
Raw lines 318 to 354Taxonomy Changes
ETE 3 examples commonly refer to:
~/.etetoolkit/taxa.sqliteETE 4 stores taxonomy data under:
~/.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:
from ete4 import GTDBTaxaNCBI numeric TaxIDs and GTDB string identifiers are not interchangeable.
Raw lines 355 to 381Visualization Migration
Raw lines 382 to 383Preferred ETE 4 SmartView
from ete4 import Tree
tree = Tree("((A,B),C);")
tree.explore()
tree.render_sm("tree.png")Custom SmartView:
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:
from ete3 import NodeStyle, TreeStyleETE 4:
from ete4.treeview import NodeStyle, TreeStyleInstall:
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:
from ete3 import ClusterTreeETE 4.4.0:
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:
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:
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"],
)Raw lines 462 to 507Mechanical Porting Checklist
Search legacy code for:
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
NodeStyleThen:
- Replace each symbol using this guide.
- Review every Newick read/write parser.
- Convert iterator consumers deliberately.
- Validate property export semantics.
- Separate SmartView and treeview layouts.
- Remove or redesign
ClusterTreeworkflows. - Test representative trees with names, support, branch lengths, NHX properties, duplicate tips, and polytomies.
- Compare scientific outputs, not just successful execution.
Verification Snippet
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);"Raw lines 554 to 569Upstream References
- Current migration guide: https://etetoolkit.github.io/ete/3to4.html
- Migration wiki: https://github.com/etetoolkit/ete/wiki/3to4
- ETE 4 release notes: https://github.com/etetoolkit/ete/releases
- ETE 4 documentation: https://etetoolkit.github.io/ete/
- ETE 4 PyPI: https://pypi.org/project/ete4/