@@ -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
145145def 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
255263def 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
305319def 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 ,
0 commit comments