From e0b2bce7ba12eb42465fded1687cd55b0ee66144 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:35:50 +0000 Subject: [PATCH] Fix front.dag against id-keyed meshed DAG.graph; run module doctests in CI meshed re-keyed DAG.graph by func_id strings, so the parents/children of a var node in dag.graph are no longer FuncNode instances and _node_replacements_for_var_node_crudification raised "must be a FuncNode". Find the producing and consuming func nodes from dag.func_nodes (out and bind) instead, which works with both the old and new graph keying. CI collected only tests/ (testpaths), so the front/dag.py doctests and front/tests never ran. testpaths now includes front, with --doctest-modules and the examples/scrap ignores in addopts, so a bare pytest runs everything. Closes #47 Co-Authored-By: Claude Opus 5.5 --- .claude/CLAUDE.md | 14 ++++++-------- front/dag.py | 44 +++++++++++++++++++++++++++++--------------- pyproject.toml | 6 ++++++ 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 8a94a49..6c30c0d 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -31,16 +31,14 @@ build on `front`; `front` itself renders nothing. ```bash uv venv .venv && uv pip install -e . pytest ruff -.venv/bin/pytest -v --tb=short # testpaths=["tests"] only: 1 passed (see gotcha) -.venv/bin/pytest --doctest-modules front/ --ignore=front/examples --ignore=front/scrap # 62 passed -.venv/bin/pytest front/tests # 15 passed — NOT in testpaths, run explicitly +.venv/bin/pytest -v --tb=short # tests/ + front/ (incl. front/tests and module doctests) .venv/bin/ruff check . ``` -**Gotcha:** `[tool.pytest.ini_options].testpaths = ["tests"]` means a bare -`pytest` run collects only the one-test `tests/test_smoke.py` — `front/tests/` -(15 tests: crude, py2pydantic, state, combos, use-case) and the module doctests -must be run explicitly, as above, to actually exercise the package. `wads -ci-local` covers all three via its own invocation; a bare `pytest` locally does not. +`[tool.pytest.ini_options]` sets `testpaths = ["tests", "front"]` and +`addopts = --doctest-modules --ignore=front/examples --ignore=front/scrap`, so a +bare `pytest` (locally and in the wads CI) runs `tests/`, `front/tests/` and every +module doctest. Before that, CI collected only `tests/`, which is how the +`front/dag.py` doctests broke unnoticed (i2mint/front#47). ## Docs diff --git a/front/dag.py b/front/dag.py index 8795964..c22187d 100644 --- a/front/dag.py +++ b/front/dag.py @@ -66,7 +66,6 @@ from meshed import DAG, FuncNode from meshed.base import ch_func_node_attrs -from meshed.itools import parents, children # TODO: Lot's of cleaning and simplification needed in this module!!! @@ -230,20 +229,40 @@ class VarNodeRole(Enum): def _get_returned_by_func_node(var_node: str, dag: DAG): - returned_by_func_node = parents(dag.graph, var_node) + """Return the ``FuncNode`` of ``dag`` whose output is ``var_node``, or ``None``. + + ``None`` means ``var_node`` is not produced by any func node: it is a root + (input) node. Func nodes are found from ``dag.func_nodes`` (not ``dag.graph``), + so this does not depend on how meshed keys its graph (``FuncNode`` instances + in older versions, ``func_id`` strings in newer ones). + + >>> from meshed import DAG, FuncNode + >>> dag = DAG([FuncNode(lambda a: a, name='f', out='x')]) + >>> _get_returned_by_func_node('x', dag) + FuncNode(a -> f -> x) + >>> _get_returned_by_func_node('a', dag) is None + True + """ + returned_by_func_node = [fn for fn in dag.func_nodes if fn.out == var_node] if len(returned_by_func_node) > 1: raise ValueError( f"This var_node had more than one parent. That is shouldn't be possible: " f"{var_node}" ) - return next( - iter(returned_by_func_node), None - ) # If None, it means it is NOT produced by a FuncNode, so is a root (input) node. + return next(iter(returned_by_func_node), None) -def _validate_is_func_node(node, var_node, relationship): - if not isinstance(node, FuncNode): - raise ValueError(f"{relationship} ({node}) of {var_node=} must be a FuncNode") +def _func_nodes_taking_var_node(var_node: str, dag: DAG): + """Yield the ``FuncNode``s of ``dag`` that take ``var_node`` as an argument. + + >>> from meshed import DAG, FuncNode + >>> def f(a): return a + >>> def g(w, y): return w + y + >>> dag = DAG([FuncNode(f, out='x'), FuncNode(g, bind={'y': 'x'})]) + >>> [fn.name for fn in _func_nodes_taking_var_node('x', dag)] + ['g_'] + """ + return (fn for fn in dag.func_nodes if var_node in fn.bind.values()) def _node_replacements_for_var_node_crudification(var_node: str, dag: DAG): @@ -278,16 +297,13 @@ def _node_replacements_for_var_node_crudification(var_node: str, dag: DAG): returned_by_func_node = _get_returned_by_func_node(var_node, dag) if returned_by_func_node is not None: - # If var_node is the output of a FuncNode - # ... make sure it is. - _validate_is_func_node(returned_by_func_node, var_node, "Parent") + # var_node is the output of a FuncNode: # yield a (func_node_name, 'return_value', var_node) triple yield returned_by_func_node.name, (VarNodeRole.return_value, var_node) # if returned_by_func_node is None, skip the above: var_node is a "root" node # arg_of_func_nodes - for arg_of_func_node in children(dag.graph, var_node): - _validate_is_func_node(arg_of_func_node, var_node, "Child") + for arg_of_func_node in _func_nodes_taking_var_node(var_node, dag): yield arg_of_func_node.name, (VarNodeRole.argument, var_node) @@ -315,8 +331,6 @@ def group_kvs_into_dict(kvs): # TODO: Skipping doctest because order not stable. Make it so -# TODO: Perhaps we should use FuncNode.name (id) instead of FuncNode itself as key of -# dag.graph. We'd have less such problems then. def _func_nodes_arg_and_return_names_to_crude( var_nodes: str | Iterable[str], dag: DAG, diff --git a/pyproject.toml b/pyproject.toml index 3d1106d..3062d08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,12 @@ convention = "google" minversion = "6.0" testpaths = [ "tests", + "front", +] +addopts = [ + "--doctest-modules", + "--ignore=front/examples", + "--ignore=front/scrap", ] doctest_optionflags = [ "NORMALIZE_WHITESPACE",