From 0460e1d022e4eaf415a4139ef310d3b861d560c7 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 10:54:01 +0100 Subject: [PATCH 01/11] Rename draw_tree function to generate_tikz --- src/draw_tree/__init__.py | 4 ++-- src/draw_tree/core.py | 12 ++++++------ tests/test_drawtree.py | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/draw_tree/__init__.py b/src/draw_tree/__init__.py index dd60563..1dd6f5a 100644 --- a/src/draw_tree/__init__.py +++ b/src/draw_tree/__init__.py @@ -8,7 +8,7 @@ __version__ = "0.1.0" from .core import ( - draw_tree, + generate_tikz, generate_tex, generate_pdf, generate_png, @@ -18,7 +18,7 @@ ) __all__ = [ - "draw_tree", + "generate_tikz", "generate_tex", "generate_pdf", "generate_png", diff --git a/src/draw_tree/core.py b/src/draw_tree/core.py index 39c46af..e3fda5e 100644 --- a/src/draw_tree/core.py +++ b/src/draw_tree/core.py @@ -1250,7 +1250,7 @@ def ef_to_tex(ef_file: str, scale_factor: float = 1.0, show_grid: bool = False) scale = original_scale grid = original_grid -def draw_tree(ef_file: str, scale_factor: float = 1.0, show_grid: bool = False) -> str: +def generate_tikz(ef_file: str, scale_factor: float = 1.0, show_grid: bool = False) -> str: """ Generate complete TikZ code from an extensive form (.ef) file. @@ -1391,8 +1391,8 @@ def generate_tex(ef_file: str, output_tex: Optional[str] = None, scale_factor: f except Exception: pass - # Generate TikZ content using draw_tree - tikz_content = draw_tree(ef_file, scale_factor, show_grid) + # Generate TikZ content using generate_tikz + tikz_content = generate_tikz(ef_file, scale_factor, show_grid) # Wrap in complete LaTeX document latex_document = latex_wrapper(tikz_content) @@ -1430,8 +1430,8 @@ def generate_pdf(ef_file: str, output_pdf: Optional[str] = None, scale_factor: f ef_path = Path(ef_file) output_pdf = ef_path.with_suffix('.pdf').name - # Generate TikZ content using draw_tree - tikz_content = draw_tree(ef_file, scale_factor, show_grid) + # Generate TikZ content using generate_tikz + tikz_content = generate_tikz(ef_file, scale_factor, show_grid) # Create LaTeX wrapper document latex_document = latex_wrapper(tikz_content) @@ -2233,7 +2233,7 @@ def emit_node(n: 'DefaultLayout.Node'): def efg_to_ef(efg_file: str) -> str: - """Convert a Gambit .efg file to the `.ef` format used by draw_tree. + """Convert a Gambit .efg file to the `.ef` format used by generate_tikz. The function implements a focused parser and deterministic layout heuristics for producing `.ef` directives from a conservative subset of diff --git a/tests/test_drawtree.py b/tests/test_drawtree.py index d2ecf3a..407c6f3 100644 --- a/tests/test_drawtree.py +++ b/tests/test_drawtree.py @@ -263,7 +263,7 @@ def test_draw_tree_basic(self): ef_file_path = ef_file.name try: - result = draw_tree.draw_tree(ef_file_path) + result = draw_tree.generate_tikz(ef_file_path) # Verify the result contains expected components assert isinstance(result, str) @@ -292,15 +292,15 @@ def test_draw_tree_with_options(self): try: # Test with scale - result_scaled = draw_tree.draw_tree(ef_file_path, scale_factor=2.0) + result_scaled = draw_tree.generate_tikz(ef_file_path, scale_factor=2.0) assert "scale=2" in result_scaled # Test with grid - result_grid = draw_tree.draw_tree(ef_file_path, show_grid=True) + result_grid = draw_tree.generate_tikz(ef_file_path, show_grid=True) assert "\\draw [help lines, color=green]" in result_grid # Test without grid (default) - result_no_grid = draw_tree.draw_tree(ef_file_path, show_grid=False) + result_no_grid = draw_tree.generate_tikz(ef_file_path, show_grid=False) assert "% \\draw [help lines, color=green]" in result_no_grid finally: @@ -310,7 +310,7 @@ def test_draw_tree_missing_files(self): """Test draw_tree with missing files.""" # Test with missing .ef file with pytest.raises(FileNotFoundError): - draw_tree.draw_tree("nonexistent.ef") + draw_tree.generate_tikz("nonexistent.ef") # Test with valid .ef file (should work with built-in macros) with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: @@ -318,7 +318,7 @@ def test_draw_tree_missing_files(self): ef_file_path = ef_file.name try: - result = draw_tree.draw_tree(ef_file_path) + result = draw_tree.generate_tikz(ef_file_path) # Should work with built-in macros assert "\\begin{tikzpicture}" in result assert "\\newcommand\\chancecolor{red}" in result From 9e73f9b799a67c8733df379f2a9b1d519ad1a80f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 11:13:20 +0100 Subject: [PATCH 02/11] add draw_tree function for Jupyter notebooks --- src/draw_tree/__init__.py | 2 ++ src/draw_tree/core.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/draw_tree/__init__.py b/src/draw_tree/__init__.py index 1dd6f5a..eef1210 100644 --- a/src/draw_tree/__init__.py +++ b/src/draw_tree/__init__.py @@ -8,6 +8,7 @@ __version__ = "0.1.0" from .core import ( + draw_tree, generate_tikz, generate_tex, generate_pdf, @@ -18,6 +19,7 @@ ) __all__ = [ + "draw_tree", "generate_tikz", "generate_tex", "generate_pdf", diff --git a/src/draw_tree/core.py b/src/draw_tree/core.py index e3fda5e..06b105c 100644 --- a/src/draw_tree/core.py +++ b/src/draw_tree/core.py @@ -15,6 +15,7 @@ from pathlib import Path from typing import List, Optional +from IPython import get_ipython # Constants DEFAULTFILE: str = "example.ef" @@ -1323,6 +1324,26 @@ def generate_tikz(ef_file: str, scale_factor: float = 1.0, show_grid: bool = Fal return tikz_code +def draw_tree(ef_file: str, scale_factor: float = 1.0, show_grid: bool = False) -> str: + """ + Generate TikZ code and display in Jupyter notebooks. + + Args: + ef_file: Path to the .ef file to process. + scale_factor: Scale factor for the diagram (default: 1.0). + show_grid: Whether to show grid lines (default: False). + + Returns: + iPython cell magic + """ + if get_ipython(): + get_ipython().run_line_magic("load_ext", "jupyter_tikz") + tikz_code = generate_tikz(ef_file, scale_factor, show_grid) + return get_ipython().run_cell_magic("tikz", "", tikz_code) + else: + raise EnvironmentError("draw_tree function requires a Jupyter notebook environment.") + + def latex_wrapper(tikz_code: str) -> str: """ Wrap TikZ code in a complete LaTeX document. From 202e93c39d9913e500a20b26296fc3f0a9d8b5a4 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 11:25:19 +0100 Subject: [PATCH 03/11] add tests for new draw_tree --- tests/test_drawtree.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_drawtree.py b/tests/test_drawtree.py index 407c6f3..7f61398 100644 --- a/tests/test_drawtree.py +++ b/tests/test_drawtree.py @@ -282,6 +282,42 @@ def test_draw_tree_basic(self): finally: os.unlink(ef_file_path) + def test_draw_tree_uses_ipython_magic(self): + """When IPython is available, draw_tree should load the extension and call the tikz cell magic.""" + from unittest.mock import Mock + # Create a simple .ef file for testing + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + ef_file.write("player 1\n") + ef_file.write("level 0 node root player 1\n") + ef_file_path = ef_file.name + + try: + fake_ip = Mock() + fake_ip.run_line_magic = Mock() + fake_ip.run_cell_magic = Mock(return_value="RENDERED") + + # Patch get_ipython in the module to return our fake IPython + with patch('draw_tree.core.get_ipython', return_value=fake_ip): + result = draw_tree.draw_tree(ef_file_path) + assert result == "RENDERED" + fake_ip.run_line_magic.assert_called_with("load_ext", "jupyter_tikz") + fake_ip.run_cell_magic.assert_called() + finally: + os.unlink(ef_file_path) + + def test_draw_tree_raises_when_no_ipython(self): + """When IPython is not available, draw_tree should raise EnvironmentError.""" + with patch('draw_tree.core.get_ipython', return_value=None): + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + ef_file.write("player 1\n") + ef_file.write("level 0 node root player 1\n") + ef_file_path = ef_file.name + try: + with pytest.raises(EnvironmentError): + draw_tree.draw_tree(ef_file_path) + finally: + os.unlink(ef_file_path) + def test_draw_tree_with_options(self): """Test draw_tree with different options.""" # Create a simple .ef file for testing From 4dfc05d728d22eff3c01a5f8822f8e69ed4f9513 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 11:31:40 +0100 Subject: [PATCH 04/11] update notebook --- tutorial/basic_usage.ipynb | 282 +++++++++++++++++++++++-------------- 1 file changed, 175 insertions(+), 107 deletions(-) diff --git a/tutorial/basic_usage.ipynb b/tutorial/basic_usage.ipynb index f4912fc..bb13254 100644 --- a/tutorial/basic_usage.ipynb +++ b/tutorial/basic_usage.ipynb @@ -2,70 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": 5, - "id": "733a7ced", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - } - ], - "source": [ - "%load_ext jupyter_tikz" - ] - }, - { - "cell_type": "code", - "execution_count": 6, + "execution_count": 1, "id": "162e2935", "metadata": {}, "outputs": [], "source": [ - "from draw_tree import draw_tree, generate_tex, generate_pdf, generate_png\n", - "\n", - "from IPython import get_ipython" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "26ae62eb", - "metadata": {}, - "outputs": [], - "source": [ - "example_games = [\n", - " \"efg/one_card_poker.efg\",\n", - " \"efg/trust_game.efg\",\n", - " \"efg/2smp.efg\",\n", - " \"efg/2s2x2x2.efg\",\n", - " \"efg/cent2.efg\",\n", - " \"one_card_poker.ef\",\n", - " \"2smp.ef\",\n", - " \"2s2x2x2.ef\",\n", - " \"cent2.ef\",\n", - " \"crossing.ef\",\n", - " \"Figure1.ef\",\n", - " \"MyTree1.ef\",\n", - " \"oldex.ef\",\n", - " \"x1.ef\",\n", - " \"efg/cross.efg\",\n", - " \"efg/holdout.efg\",\n", - "]\n", - "tikz_codes = {\n", - " game.split(\".\")[0]: draw_tree(f\"../games/{game}\", show_grid=False, scale_factor=1)\n", - " for game in example_games\n", - "}" + "from draw_tree import draw_tree, generate_tex, generate_pdf, generate_png" ] }, { "cell_type": "code", - "execution_count": 8, - "id": "c9e84d02", + "execution_count": 2, + "id": "55aebb9a-ac08-4e8b-9c60-b85274881d9f", "metadata": {}, "outputs": [ { @@ -408,21 +356,29 @@ "" ] }, - "execution_count": 8, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"one_card_poker\"])" + "draw_tree(\"../games/one_card_poker.ef\")" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 3, "id": "176cb959-b61b-43ad-b44f-3e95eafcdbf8", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -763,21 +719,29 @@ "" ] }, - "execution_count": 9, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"efg/one_card_poker\"])" + "draw_tree(\"../games/efg/one_card_poker.efg\")" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 4, "id": "5fd3f2b4-c4f2-4a6d-b8cf-3525a73e8761", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -944,21 +908,29 @@ "" ] }, - "execution_count": 10, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"efg/trust_game\"])" + "draw_tree(\"../games/efg/trust_game.efg\")" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 5, "id": "c58d058a-cd34-49e5-9b45-c336867ae0e3", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -1455,21 +1427,29 @@ "" ] }, - "execution_count": 11, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"2smp\"])" + "draw_tree(\"../games/2smp.ef\")" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 6, "id": "8ce755bf-083a-4378-aa53-5232ef24544b", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -1966,21 +1946,29 @@ "" ] }, - "execution_count": 12, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"efg/2smp\"])" + "draw_tree(\"../games/efg/2smp.efg\")" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 7, "id": "8b0d5897-76f1-4728-94e2-0e33167b6830", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -2488,21 +2476,29 @@ "" ] }, - "execution_count": 13, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"2s2x2x2\"])" + "draw_tree(\"../games/2s2x2x2.ef\")" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 8, "id": "6622c1b7-5128-43a9-9d84-0c9265d9cb13", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -3010,21 +3006,29 @@ "" ] }, - "execution_count": 14, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"efg/2s2x2x2\"])" + "draw_tree(\"../games/efg/2s2x2x2.efg\")" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 9, "id": "01d4f44e-d260-4866-bfed-00ebadab5199", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -3844,21 +3848,29 @@ "" ] }, - "execution_count": 15, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"cent2\"])" + "draw_tree(\"../games/cent2.ef\")" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 10, "id": "24d174f6-e34e-435e-97bd-fea8cfeded8e", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -4678,21 +4690,29 @@ "" ] }, - "execution_count": 16, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"efg/cent2\"])" + "draw_tree(\"../games/efg/cent2.efg\")" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 11, "id": "584e6cdc", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -4829,21 +4849,29 @@ "" ] }, - "execution_count": 17, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"crossing\"])" + "draw_tree(\"../games/crossing.ef\")" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 12, "id": "f6160e69", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -5116,21 +5144,29 @@ "" ] }, - "execution_count": 18, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"Figure1\"])" + "draw_tree(\"../games/Figure1.ef\")" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 13, "id": "22534773", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -5346,21 +5382,29 @@ "" ] }, - "execution_count": 19, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"MyTree1\"])" + "draw_tree(\"../games/MyTree1.ef\")" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 14, "id": "23d9ad2e", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -5501,21 +5545,29 @@ "" ] }, - "execution_count": 20, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"oldex\"])" + "draw_tree(\"../games/oldex.ef\")" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 15, "id": "cc060d85", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -5666,21 +5718,29 @@ "" ] }, - "execution_count": 21, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"x1\"])" + "draw_tree(\"../games/x1.ef\")" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 16, "id": "1f81020a-14da-47a4-8d1e-c3a3f3b3eaeb", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -5979,21 +6039,29 @@ "" ] }, - "execution_count": 22, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"efg/cross\"])" + "draw_tree(\"../games/efg/cross.efg\")" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 17, "id": "1b0ad93a-dfd9-4c66-b789-82ddcea13cfb", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The jupyter_tikz extension is already loaded. To reload it, use:\n", + " %reload_ext jupyter_tikz\n" + ] + }, { "data": { "image/svg+xml": [ @@ -7887,18 +7955,18 @@ "" ] }, - "execution_count": 23, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_ipython().run_cell_magic(\"tikz\", \"\", tikz_codes[\"efg/holdout\"])" + "draw_tree(\"../games/efg/holdout.efg\")" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 18, "id": "168a43f8-8609-4610-9cdd-474a8086bcc0", "metadata": {}, "outputs": [ @@ -7908,7 +7976,7 @@ "'/Users/echalstrey/projects/draw_tree/tutorial/x1.png'" ] }, - "execution_count": 24, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -7921,7 +7989,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 19, "id": "9a6167a9-e270-4874-9b4b-40887b3e4d6e", "metadata": {}, "outputs": [ @@ -7931,7 +7999,7 @@ "'/Users/echalstrey/projects/draw_tree/tutorial/one_card_poker.png'" ] }, - "execution_count": 26, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } From 69d5fab1bed12b8a10c5a5a545a94554d9577175 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 11:50:08 +0100 Subject: [PATCH 05/11] supress ext warning --- src/draw_tree/core.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/draw_tree/core.py b/src/draw_tree/core.py index 06b105c..4ad4a84 100644 --- a/src/draw_tree/core.py +++ b/src/draw_tree/core.py @@ -15,7 +15,7 @@ from pathlib import Path from typing import List, Optional -from IPython import get_ipython +from IPython.core.getipython import get_ipython # Constants DEFAULTFILE: str = "example.ef" @@ -1324,7 +1324,7 @@ def generate_tikz(ef_file: str, scale_factor: float = 1.0, show_grid: bool = Fal return tikz_code -def draw_tree(ef_file: str, scale_factor: float = 1.0, show_grid: bool = False) -> str: +def draw_tree(ef_file: str, scale_factor: float = 1.0, show_grid: bool = False) -> Optional[str]: """ Generate TikZ code and display in Jupyter notebooks. @@ -1334,12 +1334,25 @@ def draw_tree(ef_file: str, scale_factor: float = 1.0, show_grid: bool = False) show_grid: Whether to show grid lines (default: False). Returns: - iPython cell magic - """ - if get_ipython(): - get_ipython().run_line_magic("load_ext", "jupyter_tikz") + The result of the Jupyter cell magic execution, or the TikZ code string + if cell magic fails. + """ + # Ensure we are in a Jupyter notebook environment + ip = get_ipython() + if ip: + # Only attempt to load the extension if it's not already loaded + em = getattr(ip, 'extension_manager', None) + loaded = getattr(em, 'loaded', None) + try: + jpt_loaded = 'jupyter_tikz' in loaded # type: ignore + except Exception: + jpt_loaded = False + if not jpt_loaded: + ip.run_line_magic("load_ext", "jupyter_tikz") + + # Generate TikZ code and execute cell magic tikz_code = generate_tikz(ef_file, scale_factor, show_grid) - return get_ipython().run_cell_magic("tikz", "", tikz_code) + return ip.run_cell_magic("tikz", "", tikz_code) else: raise EnvironmentError("draw_tree function requires a Jupyter notebook environment.") From 07be471a28dd083a7b00c3baba992579e29f79bf Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 11:52:42 +0100 Subject: [PATCH 06/11] rerun nb --- tutorial/basic_usage.ipynb | 1891 +++++++++++++++++------------------- 1 file changed, 888 insertions(+), 1003 deletions(-) diff --git a/tutorial/basic_usage.ipynb b/tutorial/basic_usage.ipynb index bb13254..c11f4f6 100644 --- a/tutorial/basic_usage.ipynb +++ b/tutorial/basic_usage.ipynb @@ -371,14 +371,6 @@ "id": "176cb959-b61b-43ad-b44f-3e95eafcdbf8", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -734,14 +726,6 @@ "id": "5fd3f2b4-c4f2-4a6d-b8cf-3525a73e8761", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -923,14 +907,6 @@ "id": "c58d058a-cd34-49e5-9b45-c336867ae0e3", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -1442,14 +1418,6 @@ "id": "8ce755bf-083a-4378-aa53-5232ef24544b", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -1961,14 +1929,6 @@ "id": "8b0d5897-76f1-4728-94e2-0e33167b6830", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -2491,14 +2451,6 @@ "id": "6622c1b7-5128-43a9-9d84-0c9265d9cb13", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -3021,14 +2973,6 @@ "id": "01d4f44e-d260-4866-bfed-00ebadab5199", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -3863,14 +3807,6 @@ "id": "24d174f6-e34e-435e-97bd-fea8cfeded8e", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -4705,14 +4641,6 @@ "id": "584e6cdc", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -4864,14 +4792,6 @@ "id": "f6160e69", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -5159,14 +5079,6 @@ "id": "22534773", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -5397,14 +5309,6 @@ "id": "23d9ad2e", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -5560,14 +5464,6 @@ "id": "cc060d85", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -5733,14 +5629,6 @@ "id": "1f81020a-14da-47a4-8d1e-c3a3f3b3eaeb", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ @@ -6054,91 +5942,83 @@ "id": "1b0ad93a-dfd9-4c66-b789-82ddcea13cfb", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The jupyter_tikz extension is already loaded. To reload it, use:\n", - " %reload_ext jupyter_tikz\n" - ] - }, { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", @@ -6160,1795 +6040,1800 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ From 0be8f88f709e4c0ef247a05b216b40127f3ccbef Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 11:55:13 +0100 Subject: [PATCH 07/11] refactor draw_tree tests to enhance IPython magic handling --- tests/test_drawtree.py | 73 +++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/tests/test_drawtree.py b/tests/test_drawtree.py index 7f61398..85e44bf 100644 --- a/tests/test_drawtree.py +++ b/tests/test_drawtree.py @@ -282,29 +282,6 @@ def test_draw_tree_basic(self): finally: os.unlink(ef_file_path) - def test_draw_tree_uses_ipython_magic(self): - """When IPython is available, draw_tree should load the extension and call the tikz cell magic.""" - from unittest.mock import Mock - # Create a simple .ef file for testing - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: - ef_file.write("player 1\n") - ef_file.write("level 0 node root player 1\n") - ef_file_path = ef_file.name - - try: - fake_ip = Mock() - fake_ip.run_line_magic = Mock() - fake_ip.run_cell_magic = Mock(return_value="RENDERED") - - # Patch get_ipython in the module to return our fake IPython - with patch('draw_tree.core.get_ipython', return_value=fake_ip): - result = draw_tree.draw_tree(ef_file_path) - assert result == "RENDERED" - fake_ip.run_line_magic.assert_called_with("load_ext", "jupyter_tikz") - fake_ip.run_cell_magic.assert_called() - finally: - os.unlink(ef_file_path) - def test_draw_tree_raises_when_no_ipython(self): """When IPython is not available, draw_tree should raise EnvironmentError.""" with patch('draw_tree.core.get_ipython', return_value=None): @@ -318,6 +295,56 @@ def test_draw_tree_raises_when_no_ipython(self): finally: os.unlink(ef_file_path) + def test_draw_tree_calls_ipython_magic_when_available(self): + """When IPython is available, draw_tree should load the jupyter_tikz + extension if needed and call the tikz cell magic with the generated code. + """ + # Create a simple .ef file for testing + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + ef_file.write("player 1\n") + ef_file.write("level 0 node root player 1\n") + ef_file_path = ef_file.name + + class DummyEM: + def __init__(self, loaded=None): + self.loaded = loaded or set() + + class DummyIP: + def __init__(self, em): + self.extension_manager = em + self._loaded_magics = [] + self._run_cell_magic_calls = [] + + def run_line_magic(self, name, arg): + # record that load_ext was called + self._loaded_magics.append((name, arg)) + + def run_cell_magic(self, magic_name, args, code): + # record call and return a sentinel + self._run_cell_magic_calls.append((magic_name, args, code)) + return "MAGIC-RESULT" + + try: + # Case 1: extension already loaded + em = DummyEM(loaded={'jupyter_tikz'}) + ip = DummyIP(em) + with patch('draw_tree.core.get_ipython', return_value=ip): + res = draw_tree.draw_tree(ef_file_path) + # Should call run_cell_magic and return its value + assert res == "MAGIC-RESULT" + + # Case 2: extension not loaded -> run_line_magic should be called + em2 = DummyEM(loaded=set()) + ip2 = DummyIP(em2) + with patch('draw_tree.core.get_ipython', return_value=ip2): + res2 = draw_tree.draw_tree(ef_file_path) + assert res2 == "MAGIC-RESULT" + # run_line_magic should have been called to load the extension + assert ('load_ext', 'jupyter_tikz') in ip2._loaded_magics + + finally: + os.unlink(ef_file_path) + def test_draw_tree_with_options(self): """Test draw_tree with different options.""" # Create a simple .ef file for testing From 674c7348c3c7ed00e52daf94cad76c1ece746ab5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 11:58:14 +0100 Subject: [PATCH 08/11] update README --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 08559fe..da24820 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,8 @@ pip install -r requirements.txt In a Jupyter notebook, run: ```python -%load_ext jupyter_tikz from draw_tree import draw_tree -example_tikz = draw_tree('games/example.ef') -get_ipython().run_cell_magic("tikz", "", example_tikz) +draw_tree('games/example.ef') ``` ## Developer docs: Testing From fc26cea3f3297ef1ee21cc12926591c58c043fd4 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 12:01:09 +0100 Subject: [PATCH 09/11] mention efg --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index da24820..5255d38 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Game tree drawing tool for extensive form games that generates TikZ code, LaTeX documents, PDFs, and PNGs. +Pass in an extensive form game file in `.ef` format with layout formatting, and `draw_tree` will generate a visual representation of the game tree. +You can also pass in a file in `.efg` format, which will be converted to `.ef` internally, applying a default layout. + ## Installation Clone the repo and install the package using pip: From e66a90cd883b97eb29f49d721488c9a28fa3fd77 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 12:04:07 +0100 Subject: [PATCH 10/11] refactor dependencies: move jupyter-tikz and ipykernel to required runtime dependencies and remove requirements.txt --- README.md | 5 ----- pyproject.toml | 4 +++- requirements.txt | 4 ---- 3 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 requirements.txt diff --git a/README.md b/README.md index 5255d38..5be249e 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,6 @@ generate_png('games/example.ef', output_png='mygame.png', scale_factor=0.8) # ### Rendering in Jupyter Notebooks -First install the requirements, which include the `jupyter-tikz` extension: -```bash -pip install -r requirements.txt -``` - In a Jupyter notebook, run: ```python diff --git a/pyproject.toml b/pyproject.toml index 6ef4d10..ca3c578 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,10 @@ classifiers = [ ] keywords = ["game theory", "tikz", "visualization", "trees", "economics"] +# Required runtime dependencies (previously optional under the 'jupyter' extra) +dependencies = ["jupyter-tikz", "ipykernel"] + [project.optional-dependencies] -jupyter = ["jupyter-tikz", "ipykernel"] dev = ["pytest>=7.0.0", "pytest-cov"] [project.scripts] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ee4fa39..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -jupyter-tikz -ipykernel -pytest>=7.0.0 -pytest-cov \ No newline at end of file From 25318b4b925e1e7849823c4ca3775f6ad61e0331 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 23 Oct 2025 13:06:31 +0100 Subject: [PATCH 11/11] min python 3.10 --- .github/workflows/test.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc65c28..de9463d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.13"] + python-version: ["3.10", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 5be249e..754c2c3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ pip install -e . ## Requirements -- Python 3.9+ (tested on 3.13) +- Python 3.10+ (tested on 3.13) - LaTeX with TikZ (for PDF/PNG generation) - (optional) ImageMagick or Ghostscript or Poppler (for PNG generation)