Skill Tracer
Skills › Scientific Agent Skills › etetoolkit › migration-ete3-to-ete4.md

migration-ete3-to-ete4.md

576 lines · skills/etetoolkit/references/migration-ete3-to-ete4.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Migrating ETE 3 Code to ETE 4
2
3ETE 4 is a breaking API revision, not an import-only upgrade. This guide targets
4ETE 4.4.0 and summarizes the official migration guide plus behavior verified
5against the installed release.
6

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 6
7## Release Baseline
8
9- ETE 4.0.0 and 4.1.1 were released March 28, 2025.
10- ETE 4.1.1 marked ETE 4 as out of beta and available on PyPI.
11- ETE 4.4.0 was released September 3, 2025.
12- Package name and primary import are `ete4`.
14Install side by side only when a legacy project genuinely requires ETE 3:
16```bash
17uv pip install "ete4==4.4.0"
18```
20Do not leave both APIs implicit in one code path. Name compatibility boundaries
21and test them separately.

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:

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 22
23## Import Changes
25ETE 3:
27```python
28from ete3 import NCBITaxa, PhyloTree, Tree
29```
31ETE 4:
33```python
34from ete4 import GTDBTaxa, NCBITaxa, PhyloTree, Tree
35```
37ETE 3 exposed equivalent `TreeNode` and `Tree` classes. ETE 4 uses `Tree`.
39Qt visualization imports moved:
41```python
42# ETE 3
43from ete3 import NodeStyle, TextFace, TreeStyle
45# ETE 4
46from ete4.treeview import NodeStyle, TextFace, TreeStyle
47```
49Current web visualization uses:
51```python
52from ete4.smartview import Layout, PropFace, TextFace
53```
55SmartView and treeview `TextFace` classes are different types.

Import Changes

ETE 3:

from ete3 import NCBITaxa, PhyloTree, Tree

ETE 4:

from ete4 import GTDBTaxa, NCBITaxa, PhyloTree, Tree

ETE 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, TreeStyle

Current web visualization uses:

from ete4.smartview import Layout, PropFace, TextFace

SmartView and treeview TextFace classes are different types.

Raw lines 23 to 56
57## Construction and File Input

Construction and File Input

Raw lines 57 to 58
59### File path ambiguity was removed
61ETE 3:
63```python
64tree = Tree("tree.nw", format=1)
65```
67ETE 4:
69```python
70from pathlib import Path
72with Path("tree.nw").open(encoding="utf-8") as handle:
73 tree = Tree(handle, parser=1)
74```
76Use strings for Newick text and pass an open file object for file input. ETE
774.4.0 still contains a path-like string heuristic internally, but relying on it
78conflicts with the documented contract and makes input behavior ambiguous.

File 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 79
80### New nodes with properties
82ETE 3:
84```python
85tree = Tree(name="root", dist=0, support=1)
86```
88ETE 4:
90```python
91tree = Tree({"name": "root", "dist": 0, "support": 1})
92```
94ETE 4 accepts arbitrary initial properties through the dictionary.

New 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 95
96## Property Model
98ETE 3 required name, distance, and support defaults. In ETE 4 these properties
99can be absent, and their convenience accessors can return `None`.
101ETE 3:
103```python
104node.add_feature("habitat", "marine")
105node.add_features(group="case", score=0.8)
106print(node.features)
107```
109ETE 4:
111```python
112node.add_prop("habitat", "marine")
113node.add_props(group="case", score=0.8)
114print(node.props)
115```
117General argument renames:
119- `feature` / `features` → `prop` / `props`
120- `attribute` / `attributes` → `prop` / `props`
121- `property` / `properties` → `prop` / `props`
123Replace `hasattr(node, "x")` tests for custom metadata with:
125```python
126if "x" in node.props:
127 value = node.props["x"]
128```

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:

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 / props
  • attribute / attributes → prop / props
  • property / 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 129
130## Lookup, Predicates, and Relatives
132| ETE 3 | ETE 4 |
133|---|---|
134| `tree & "A"` | `tree["A"]` |
135| `tree.get_tree_root()` | `tree.root` |
136| `node.is_leaf()` | `node.is_leaf` |
137| `node.is_root()` | `node.is_root` |
138| `tree.get_common_ancestor(a, b)` | `tree.common_ancestor(a, b)` |
139| `node.get_ancestors()` | `node.ancestors()` |
140| `tree.get_leaves_by_name("A")` | `tree.search_leaves_by_name("A")` |
142ETE 4 also supports positional IDs:
144```python
145node = tree[0, 1, 0]
146print(node.id, node.level)
147```
149Name lookup returns the first match in both practical patterns. Validate
150uniqueness when names are identifiers.

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:

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 151
152## Iterator Renames
154| ETE 3 | ETE 4 |
155|---|---|
156| `get_leaves()` / `iter_leaves()` | `leaves()` |
157| `get_descendants()` / `iter_descendants()` | `descendants()` |
158| `get_edges()` / `iter_edges()` | `edges()` |
159| `get_leaf_names()` | `leaf_names()` |
160| `get_ancestors()` | `ancestors()` |
162ETE 4 returns iterators:
164```python
165leaves = list(tree.leaves())
166names = list(tree.leaf_names())
167```
169Do not call `len(tree.leaves())` or index the result without first creating a
170list.

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:

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

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

Raw lines 152 to 171
172## Text and Newick I/O

Text and Newick I/O

Raw lines 172 to 173
174### Parser rename
176ETE 3:
178```python
179tree = Tree(newick, format=1)
180newick = tree.write(format=1)
181```
183ETE 4:
185```python
186tree = Tree(newick, parser=1)
187newick = tree.write(parser=1)
188```
190Named parser aliases include `"name"` and `"support"`.

Parser 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".

Raw lines 174 to 191
192### ASCII rename
194ETE 3:
196```python
197print(tree.get_ascii(show_internal=True))
198```
200ETE 4:
202```python
203print(tree.to_str(show_internal=True, props=["name", "dist"]))
204```

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 205
206### Extended-property semantics
208ETE 3 `features=[]` meant all available features. In ETE 4:
210```python
211tree.write(props=[]) # no extended properties
212tree.write(props=["species", "host"]) # selected properties
213tree.write(props=None) # all available properties
214```
216This reversal is important. Use an explicit selected list for external output.
218Use keyword arguments with `write()`. Its first positional argument is
219`outfile` in ETE 4, not the ETE 3 feature selection.

Extended-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 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.

Raw lines 206 to 220
221### Custom formatters
223ETE 3:
225```python
226newick = tree.write(
227 format=1,
228 dist_formatter="%0.1f",
229 name_formatter="TEST-%s",
231```
233ETE 4:
235```python
236from ete4.parser import newick
238parser = newick.make_parser(
239 1,
240 dist="%0.1f",
241 name="TEST-%s",
243text = tree.write(parser=parser)
244```

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 245
246## Distances and Topology
248| ETE 3 | ETE 4 |
249|---|---|
250| `A.get_distance(B)` | `tree.get_distance(A, B)` |
251| `topology_only=True` | `topological=True` |
252| `convert_to_ultrametric()` | `to_ultrametric()` |
253| `resolve_polytomy(recursive=True)` | `resolve_polytomy(descendants=True)` |
255ETE 4 adds a direct midpoint convenience:
257```python
258tree.set_midpoint_outgroup()
259```
261`get_midpoint_outgroup()` identifies the edge containing the midpoint, but
262`set_outgroup()` defaults to a different cut position on that edge. Use
263`set_midpoint_outgroup()` when exact placement is required.
265ETE 4.4.0 adds `distance_matrix()`, which supersedes
266`cophenetic_matrix()` for new code.

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:

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.

Raw lines 246 to 267
268## Random Tree Generation
270ETE 3:
272```python
273tree.populate(
274 size,
275 names_library=names,
276 random_branches=True,
277 dist_range=(0, 1),
279```
281ETE 4:
283```python
284import random
286tree.populate(
287 size,
288 names=names,
289 model="yule",
290 dist_fn=random.random,
291 support_fn=lambda: 1,
293```
295Set the random seed when generated topology or distances must be reproducible.

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 296
297## Robinson-Foulds Unpacking
299ETE 4.4.0 returns seven values:
301```python
303 rf,
304 max_rf,
305 common,
306 edges_self,
307 edges_other,
308 discarded_self,
309 discarded_other,
310) = tree.robinson_foulds(other)
311```
313ETE 3 examples that unpack only five values must be updated.
315Argument names also use `prop_t1` and `prop_t2` rather than feature-oriented
316names.

Robinson-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.

Raw lines 297 to 317
318## PhyloTree Changes and Traps
320The central ETE 3 methods remain, but use ETE 4 property and iterator syntax:
322```python
323from ete4 import PhyloTree
325tree = PhyloTree(
326 "((Hsa|g1,Ptr|g1),Mmu|g1);",
327 sp_naming_function=lambda name: name.split("|", 1)[0],
330events = tree.get_descendant_evol_events(sos_thr=0.0)
331for leaf in tree.leaves():
332 print(leaf.name, leaf.species)
333```
335Pass `sp_naming_function` explicitly for species-aware methods. The current
336source defaults it to `None`, despite older documentation describing an
337automatic first-three-character rule. Species-overlap event detection also
338requires a rooted, fully bifurcating gene tree.
340Do not pass a species tree to `get_descendant_evol_events()`. In ETE 4.4.0 its
341signature accepts only `sos_thr`. Use reconciliation:
343```python
344reconciled_tree, events = gene_tree.reconcile(species_tree)
345```
347After event detection, inspect:
349```python
350node.props.get("evoltype")
351```
353rather than relying on ETE 3 feature helpers.

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 354
355## Taxonomy Changes
357ETE 3 examples commonly refer to:
359```text
360~/.etetoolkit/taxa.sqlite
361```
363ETE 4 stores taxonomy data under:
365```text
366~/.local/share/ete/
367```
369The current documentation's approximately 600 MB NCBI and 72 MB GTDB figures
370are better treated as local first-use footprint estimates, not compressed
371network download sizes. Archive sizes vary by release and can be much smaller;
372allow extra space for parsed SQLite and temporary conversion files.
374ETE 4 adds first-class GTDB support:
376```python
377from ete4 import GTDBTaxa
378```
380NCBI numeric TaxIDs and GTDB string identifiers are not interchangeable.

Taxonomy Changes

ETE 3 examples commonly refer to:

~/.etetoolkit/taxa.sqlite

ETE 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 GTDBTaxa

NCBI numeric TaxIDs and GTDB string identifiers are not interchangeable.

Raw lines 355 to 381
382## Visualization Migration

Visualization Migration

Raw lines 382 to 383
384### Preferred ETE 4 SmartView
386```python
387from ete4 import Tree
389tree = Tree("((A,B),C);")
390tree.explore()
391tree.render_sm("tree.png")
392```
394Custom SmartView:
396```python
397from ete4.smartview import Layout, PropFace
400def draw_node(node):
401 if node.is_leaf:
402 return PropFace("name", position="right")
405layout = Layout("labels", draw_node=draw_node)
406tree.explore(layouts=[layout])
407```
409SmartView style dictionaries and faces are not compatible with `TreeStyle` or
410`NodeStyle`.

Preferred 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.

Raw lines 384 to 411
412### Retained Qt treeview
414ETE 3:
416```python
417from ete3 import NodeStyle, TreeStyle
418```
420ETE 4:
422```python
423from ete4.treeview import NodeStyle, TreeStyle
424```
426Install:
428```bash
429uv pip install "ete4[treeview]==4.4.0"
430```
432Qt treeview remains the option for vector PDF/SVG. SmartView's `render_sm()` in
433ETE 4.4.0 creates PNG screenshot data.

Retained Qt treeview

ETE 3:

from ete3 import NodeStyle, TreeStyle

ETE 4:

from ete4.treeview import NodeStyle, TreeStyle

Install:

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.

Raw lines 412 to 434
435## Clustering
437ETE 3:
439```python
440from ete3 import ClusterTree
441```
443ETE 4.4.0:
445```text
446ImportError: cannot import name 'ClusterTree' from 'ete4'
447```
449Do not document `ClusterTree`, linked matrix profiles, silhouette, or Dunn
450methods as ETE 4 capabilities. Use a maintained clustering library for those
451calculations and a normal ETE `Tree` for topology display.

Clustering

ETE 3:

from ete3 import ClusterTree

ETE 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.

Raw lines 435 to 452
453## Command-Line Caveat
455The `ete4 compare` command shipped in ETE 4.4.0 still calls `Tree(...,
456format=...)` internally and fails with the removed keyword. Use
457`Tree.robinson_foulds()`, `Tree.compare()` for unique labels, or this skill's
458`scripts/tree_operations.py compare` helper. Avoid the duplication-aware
459`Tree.compare(has_duplications=True)` path as well; upstream source labels that
460branch as likely broken.

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.

Raw lines 453 to 461
Runs code scripts/tree_operations.py :458
462## Porting Example
464ETE 3:
466```python
467from ete3 import Tree
469tree = Tree("tree.nw", format=1)
470node = tree & "A"
471node.add_feature("group", "case")
473for leaf in tree.iter_leaves():
474 if leaf.is_leaf():
475 print(leaf.name)
477tree.write(
478 outfile="out.nhx",
479 format=1,
480 features=["group"],
482```
484ETE 4:
486```python
487from pathlib import Path
489from ete4 import Tree
491with Path("tree.nw").open(encoding="utf-8") as handle:
492 tree = Tree(handle, parser=1)
494node = tree["A"]
495node.add_prop("group", "case")
497for leaf in tree.leaves():
498 if leaf.is_leaf:
499 print(leaf.name)
501tree.write(
502 outfile="out.nhx",
503 parser=1,
504 props=["group"],
506```

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 507
508## Mechanical Porting Checklist
510Search legacy code for:
512```text
513from ete3
514TreeNode
515format=
516features=
517feature=
518attributes=
519attribute=
520add_feature
521add_features
522.features
523get_ascii
524get_tree_root
525get_common_ancestor
526get_leaves
527iter_leaves
528get_descendants
529iter_descendants
530get_leaf_names
531get_leaves_by_name
532convert_to_ultrametric
533topology_only
534is_leaf()
535is_root()
536 & "
537ClusterTree
538TreeStyle
539NodeStyle
540```
542Then:
5441. Replace each symbol using this guide.
5452. Review every Newick read/write parser.
5463. Convert iterator consumers deliberately.
5474. Validate property export semantics.
5485. Separate SmartView and treeview layouts.
5496. Remove or redesign `ClusterTree` workflows.
5507. Test representative trees with names, support, branch lengths, NHX
551 properties, duplicate tips, and polytomies.
5528. Compare scientific outputs, not just successful execution.

Mechanical 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
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.
Raw lines 508 to 553
554## Verification Snippet
556```python
557import ete4
558from ete4 import Tree
560assert ete4.__version__ == "4.4.0"
562tree = Tree("((A:1,B:1)95:0.2,C:1);", parser="support")
563assert list(tree.leaf_names()) == ["A", "B", "C"]
564assert tree["A"].is_leaf
566round_trip = tree.write(parser="support", props=[])
567assert round_trip == "((A:1,B:1)95:0.2,C:1);"
568```

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 569
570## Upstream References
572- Current migration guide: https://etetoolkit.github.io/ete/3to4.html
573- Migration wiki: https://github.com/etetoolkit/ete/wiki/3to4
574- ETE 4 release notes: https://github.com/etetoolkit/ete/releases
575- ETE 4 documentation: https://etetoolkit.github.io/ete/
576- ETE 4 PyPI: https://pypi.org/project/ete4/

Upstream 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/
Raw lines 570 to 576