Skip to content
Merged
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
77 changes: 68 additions & 9 deletions meshed/tests/test_ch_funcs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
"""Tests for ``meshed.dag.ch_funcs`` -- changing the functions of existing func nodes.

Besides the behaviour of ``ch_funcs`` itself, this module pins how ``ch_funcs`` may be
*called*: it is built with ``i2.double_up_as_factory``, so it must decorate when given
func nodes and only make a factory when not given any -- whether the func nodes are
passed positionally or by keyword.
"""

from functools import partial
from typing import NamedTuple

import meshed as ms
import pytest

import meshed.base
import meshed.util
from meshed.dag import ch_funcs, _validate_func_mapping
from meshed.dag import DAG, ch_funcs, _validate_func_mapping
from meshed.tests.objects_for_testing import f, g
from meshed.base import compare_signatures
from i2 import Sig
from typing import NamedTuple
from i2 import Sig, double_up_as_factory


def _wrapped_by_keyword_is_supported() -> bool:
"""Whether the installed ``i2`` accepts a decorator's wrapped object by keyword.

Before `i2mint/i2#82 <https://github.com/i2mint/i2/pull/82>`_,
``double_up_as_factory`` decided between "decorate this" and "make a factory" by
looking only at the first *positional* argument. A wrapped object passed by
keyword therefore landed in ``**kwargs`` and the decorator silently returned a
``functools.partial`` factory instead of the decorated object.
"""

@double_up_as_factory
def probe(obj=None, *, unused=None):
return obj

return probe(obj=int) is int


#: Whether ``ch_funcs(func_nodes=...)`` decorates (True) or wrongly returns a factory
#: (False). Depends on the installed ``i2`` -- see ``_wrapped_by_keyword_is_supported``.
I2_SUPPORTS_WRAPPED_BY_KEYWORD = _wrapped_by_keyword_is_supported()


@pytest.fixture
Expand All @@ -25,18 +57,45 @@ def example_func_mapping():


def test_ch_funcs_no_change(example_func_nodes):
"""Mapping every func node to the function it already has changes nothing."""
funcs = [f, g]
nodes = list(example_func_nodes)
names = [node.name for node in nodes]

dummy_mapping = dict(zip(names, funcs))

new_dag = ch_funcs(
func_nodes=nodes,
func_mapping=dummy_mapping,
)
new_nodes = new_dag().func_nodes
assert nodes == new_nodes
new_dag = ch_funcs(nodes, func_mapping=dummy_mapping)

assert isinstance(new_dag, DAG)
assert nodes == new_dag.func_nodes


@pytest.mark.skipif(
not I2_SUPPORTS_WRAPPED_BY_KEYWORD,
reason=(
"installed i2 predates i2mint/i2#82, so a double_up_as_factory decorator "
"given its wrapped object by keyword wrongly returns a factory"
),
)
def test_ch_funcs_takes_func_nodes_by_keyword(example_func_nodes):
"""``ch_funcs(func_nodes=...)`` must decorate, not return a factory.

Regression pin for `i2mint/i2#82 <https://github.com/i2mint/i2/pull/82>`_: passing
the func nodes by keyword must mean exactly what passing them positionally means.
Before that fix it returned a ``functools.partial``, and call sites papered over it
with a trailing ``()`` -- which this test exists to stop coming back.
"""
funcs = [f, g]
nodes = list(example_func_nodes)
dummy_mapping = dict(zip([node.name for node in nodes], funcs))

new_dag = ch_funcs(func_nodes=nodes, func_mapping=dummy_mapping)

assert not isinstance(new_dag, partial), "ch_funcs wrongly returned a factory"
assert isinstance(new_dag, DAG)
assert nodes == new_dag.func_nodes
# ...and it agrees with the positional form
assert new_dag.func_nodes == ch_funcs(nodes, func_mapping=dummy_mapping).func_nodes


class FlagWithMessage(NamedTuple):
Expand Down
Loading