Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
44 changes: 29 additions & 15 deletions front/dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -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!!!
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading