Skip to content

Commit 4466a75

Browse files
committed
FIX(MODIFS): OPTIONALS Always KW-types, ...
- FIX: revert automatic introduction of `fnkwarg` on Renames, to old-name, bc positionals suddenly become KWs. + fix: optional Sideffecteds behave like optionals. + FIX(TC): discovered and fixed TC abusing optionals instead of vararg. + enh: new assertion for parsing _Optionals arg. + DOC: explain a bit of these in the comments.
1 parent 71096bd commit 4466a75

File tree

4 files changed

+65
-31
lines changed

4 files changed

+65
-31
lines changed

graphtik/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@
4242
## SEE ALSO: `.plot.active_plotter_plugged()`, `.plot.set_active_plotter()` &
4343
# `.plot.get_active_plotter()` configs, not imported, unless plot is needed..
4444

45-
from .modifiers import mapped, optional, vararg, varargs, sideffect, sideffected, rename_dependency
45+
from .modifiers import (
46+
mapped,
47+
optional,
48+
vararg,
49+
varargs,
50+
sideffect,
51+
sideffected,
52+
)
4653
from .netop import compose
4754
from .network import AbortedException, IncompleteExecutionError
4855
from .op import operation, NULL_OP

graphtik/modifiers.py

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,19 @@ def __new__(
6464
# - string-name on sideffects and
6565
# - repr for all
6666
#
67+
assert not optional or _Optionals(optional), ("Invalid optional: ", locals())
6768
if optional and optional.varargish:
68-
assert not fn_kwarg and not sideffects, (
69+
assert not fn_kwarg, (
6970
"Varargish cannot map `fn_kwargs` or sideffects:",
70-
name,
71-
fn_kwarg,
72-
optional,
73-
optional,
74-
sideffects,
71+
locals(),
7572
)
7673
_repr = f"{optional.name}({str(name)!r})"
7774
else:
7875
if sideffects is not None:
7976
if sideffects == ():
8077
assert fn_kwarg is None, (
8178
"Pure sideffects cannot map `fn_kwarg`:",
82-
name,
83-
fn_kwarg,
84-
optional,
85-
sideffects,
79+
locals(),
8680
)
8781

8882
# Repr display also optionality (irrelevant to object's identity)
@@ -97,12 +91,18 @@ def __new__(
9791
# (irrelevant to object's identity)
9892
#
9993
qmark = "?" if optional else ""
100-
map_str = f", fn_kwarg={fn_kwarg!r}" if fn_kwarg else ""
94+
# Mapped string is so convoluted bc it mimics `optional`
95+
# when `fn_kwarg` given.
96+
map_str = (
97+
f", fn_kwarg={fn_kwarg!r}"
98+
if fn_kwarg and fn_kwarg != name
99+
else ""
100+
)
101101
_repr = f"sideffected{qmark}({str(name)!r}<--{sfx_str}{map_str})"
102102

103103
name = f"sideffected({str(name)!r}<--{sfx_str})"
104104
elif optional or fn_kwarg:
105-
map_str = f"-->{fn_kwarg!r}" if fn_kwarg else ""
105+
map_str = f"-->{fn_kwarg!r}" if fn_kwarg != name else ""
106106
_repr = (
107107
f"{'optional' if optional else 'mapped'}({str(name)!r}{map_str})"
108108
)
@@ -143,7 +143,11 @@ def withset(self, **kw):
143143

144144

145145
def is_mapped(dep) -> Optional[str]:
146-
"""Check if a :term:`dependency` is mapped (and get it)."""
146+
"""
147+
Check if a :term:`dependency` is mapped (and get it).
148+
149+
Note that all non-varargish optionals are mapped (including sideffected optionals).
150+
"""
147151
return getattr(dep, "fn_kwarg", None)
148152

149153

@@ -198,12 +202,9 @@ def rename_dependency(dep, ren):
198202
else:
199203
old_name = dep.sideffected if is_sideffected(dep) else str(dep)
200204
new_name = renamer(old_name)
201-
kw = {}
202-
if dep.fn_kwarg is None and not is_varargish(dep):
203-
kw["fn_kwarg"] = old_name
204-
dep = dep.withset(name=new_name, **kw)
205+
dep = dep.withset(name=new_name)
205206
else: # plain string
206-
dep = mapped(renamer(dep), str(dep))
207+
dep = renamer(dep)
207208

208209
return dep
209210

@@ -212,15 +213,22 @@ def mapped(name: str, fn_kwarg: str):
212213
"""
213214
Annotate a :term:`needs` that (optionally) map `inputs` name --> argument-name.
214215
216+
The value of a mapped dependencies is passed in as *keyword argument*
217+
to the underlying function.
218+
215219
:param fn_kwarg:
216220
The argument-name corresponding to this named-input.
217-
If not given, a regular string is returned.
221+
If it is None, assumed the same as `name`, so as
222+
to behave always like kw-type arg and to preserve fn-name if ever renamed.
218223
219224
.. Note::
220225
This extra mapping argument is needed either for :term:`optionals`
221226
(but not :term:`varargish`), or for functions with keywords-only arguments
222227
(like ``def func(*, foo, bar): ...``),
223228
since `inputs` are normally fed into functions by-position, not by-name.
229+
:return:
230+
a :class:`_Modifier` instance, even if no `fn_kwarg` is given OR
231+
it is the same as `name`.
224232
225233
**Example:**
226234
@@ -249,16 +257,22 @@ def mapped(name: str, fn_kwarg: str):
249257
250258
.. graphtik::
251259
"""
252-
return _Modifier(name, fn_kwarg=fn_kwarg) if fn_kwarg else name
260+
return _Modifier(name, fn_kwarg=fn_kwarg or name)
253261

254262

255263
def optional(name: str, fn_kwarg: str = None):
256264
"""
257265
Annotate :term:`optionals` `needs` corresponding to *defaulted* op-function arguments, ...
258266
259267
received only if present in the `inputs` (when operation is invoked).
260-
The value of an optional is passed as a keyword argument to the underlying function.
261268
269+
The value of an optional is passed in as a *keyword argument*
270+
to the underlying function.
271+
272+
:param fn_kwarg:
273+
the name for the function argument it corresponds;
274+
if a falsy is given, same as `name` assumed,
275+
to behave always like kw-type arg and to preserve fn-name if ever renamed.
262276
263277
**Example:**
264278
@@ -299,7 +313,7 @@ def optional(name: str, fn_kwarg: str = None):
299313
fn='myadd')
300314
301315
"""
302-
return _Modifier(name, fn_kwarg=fn_kwarg, optional=_Optionals.optional)
316+
return _Modifier(name, fn_kwarg=fn_kwarg or name, optional=_Optionals.optional)
303317

304318

305319
def vararg(name: str):
@@ -502,6 +516,12 @@ def sideffected(
502516
r"""
503517
Annotates a :term:`sideffected` dependency in the solution sustaining side-effects.
504518
519+
:param fn_kwarg:
520+
the name for the function argument it corresponds.
521+
When optional, it becomes the same as `name` if falsy, so as
522+
to behave always like kw-type arg and to preserve fn-name if ever renamed.
523+
When not optional, if not given, it's all fine.
524+
505525
Like :func:`.sideffect` but annotating a *real* :term:`dependency` in the solution,
506526
allowing that dependency to be present both in :term:`needs` and :term:`provides`
507527
of the same function.
@@ -595,6 +615,11 @@ def sideffected(
595615
f"Expecting a non-sideffect for sideffected"
596616
f", got: {type(dependency).__name__}({dependency!r})"
597617
)
618+
## Mimic `optional` behavior,
619+
# i.e. preserve kwarg-ness if optional.
620+
#
621+
if optional and not fn_kwarg:
622+
fn_kwarg = dependency
598623
return _Modifier(
599624
dependency,
600625
sideffects=sideffects,

test/test_graphtik.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,7 @@ def test_rescheduling(exemethod, resched, rescheduled):
14071407
operation(
14081408
lambda *args: sum(args),
14091409
name="op3",
1410-
needs=["a", optional("b"), optional("c")],
1410+
needs=["a", vararg("b"), vararg("c")],
14111411
provides=["d"],
14121412
),
14131413
parallel=exemethod,

test/test_modifiers.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def test_modifs_str(mod, exp):
4141
@pytest.mark.parametrize(
4242
"mod, exp",
4343
[
44-
(lambda: mapped("b", None), "'b'"),
45-
(lambda: mapped("b", ""), "'b'"),
44+
(lambda: mapped("b", None), "mapped('b')"),
45+
(lambda: mapped("b", ""), "mapped('b')"),
4646
(lambda: mapped("b", "bb"), "mapped('b'-->'bb')"),
4747
(lambda: optional("b"), "optional('b')"),
4848
(lambda: optional("b", "bb"), "optional('b'-->'bb')"),
@@ -103,7 +103,7 @@ def test_withset_bad_kwargs():
103103
@pytest.mark.parametrize(
104104
"mod, exp",
105105
[
106-
(lambda: "b", "mapped('p.b'-->'b')"),
106+
(lambda: "b", "'p.b'"),
107107
(lambda: mapped("b", None), "mapped('p.b'-->'b')"),
108108
(lambda: mapped("b", ""), "mapped('p.b'-->'b')"),
109109
(lambda: mapped("b", "bb"), "mapped('p.b'-->'bb')"),
@@ -113,10 +113,7 @@ def test_withset_bad_kwargs():
113113
(lambda: varargs("d"), "varargs('p.d')"),
114114
(lambda: sideffect("e"), "sideffect: 'e'"),
115115
(lambda: sideffect("e", optional=1), "sideffect?: 'e'"),
116-
(
117-
lambda: sideffected("f", "a", "b"),
118-
"sideffected('p.f'<--'a', 'b', fn_kwarg='f')",
119-
),
116+
(lambda: sideffected("f", "a", "b"), "sideffected('p.f'<--'a', 'b')",),
120117
(
121118
lambda: sideffected("f", "ff", fn_kwarg="F"),
122119
"sideffected('p.f'<--'ff', fn_kwarg='F')",
@@ -142,6 +139,7 @@ def test_modifs_rename_fn(mod, exp):
142139
@pytest.mark.parametrize(
143140
"mod, exp",
144141
[
142+
(lambda: "s", "'D'"),
145143
(lambda: mapped("b", "bb"), "mapped('D'-->'bb')"),
146144
(lambda: optional("b"), "optional('D'-->'b')"),
147145
(lambda: optional("b", "bb"), "optional('D'-->'bb')"),
@@ -152,6 +150,10 @@ def test_modifs_rename_fn(mod, exp):
152150
lambda: sideffected("f", "a", "b", optional=1,),
153151
"sideffected?('D'<--'a', 'b', fn_kwarg='f')",
154152
),
153+
(
154+
lambda: sideffected("f", "a", "b", optional=1, fn_kwarg="F"),
155+
"sideffected?('D'<--'a', 'b', fn_kwarg='F')",
156+
),
155157
],
156158
)
157159
def test_modifs_rename_str(mod, exp):

0 commit comments

Comments
 (0)