Skip to content

Commit 3da25a4

Browse files
committed
FEAT(FNOP+.tc+plot+doc) KW(Provides) RENAME Rets-dict
1 parent 66265be commit 3da25a4

File tree

4 files changed

+84
-34
lines changed

4 files changed

+84
-34
lines changed

docs/source/operations.rst

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ to the :func:`.operation` factory, specifically:
154154
Declarations of *needs* and *provides* is affected by :term:`modifier`\s like
155155
:func:`.keyword`:
156156

157-
Map inputs to different function arguments
158-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
157+
Map inputs(& outputs) to differently named function arguments (& results)
158+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
159159
.. autofunction:: graphtik.modifier.keyword
160160
:noindex:
161161

@@ -174,22 +174,29 @@ Calling functions with varargs (``*args``)
174174

175175
.. _aliases:
176176

177-
Aliased `provides`
178-
^^^^^^^^^^^^^^^^^^
177+
Interface differently named dependencies: aliases & keyword modifier
178+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
179179
Sometimes, you need to interface functions & operations where they name a
180-
:term:`dependency` differently.
181-
This is doable without introducing "pipe-through" interface operation, either
182-
by annotating certain `needs` with :func:`.keyword` *modifier* (above), or
183-
by :term:`alias`\sing certain `provides` to different names:
180+
:term:`dependency` differently. There are 4 different ways to accomplish that:
184181

185-
>>> op = operation(str,
186-
... name="`provides` with `aliases`",
187-
... needs="anything",
188-
... provides="real thing",
189-
... aliases=("real thing", "phony"))
190182

191-
.. graphtik::
183+
1. Introduce some :term:`"pipe-through" operation <conveyor operation>`
184+
(see the example in :ref:`conveyor-function`, below).
185+
186+
2. Annotate certain `needs` with :func:`.keyword` *modifier*
187+
(exemplified in the modifier).
188+
189+
3. For a :term:`returns dictionary` operation, annotate certain `provides`
190+
with a :func:`.keyword` *modifier* (exemplified in the modifier).
191+
192+
4. :term:`Alias <alias>` (clone) certain `provides` to different names:
193+
194+
>>> op = operation(str,
195+
... name="cloning `provides` with an `alias`",
196+
... provides="real thing",
197+
... aliases=("real thing", "clone"))
192198

199+
.. graphtik::
193200

194201
.. _conveyor-function:
195202

graphtik/fnop.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,14 @@ def _zip_results_returns_dict(self, results, is_rescheduled) -> dict:
689689
f"\n {debug_var_tip}"
690690
)
691691

692-
fn_required = fn_expected = self._fn_provides
692+
fn_required = self._fn_provides
693+
if fn_required:
694+
renames = {get_keyword(i): i for i in fn_required} # +1 useless key: None
695+
renames.pop(None, None)
696+
fn_expected = fn_required = [get_keyword(i) or i for i in fn_required]
697+
else:
698+
fn_expected = fn_required = renames = ()
699+
693700
if is_rescheduled:
694701
# Canceled sfx(ed) are welcomed.
695702
fn_expected = iset([*fn_expected, *(i for i in self.provides if is_sfx(i))])
@@ -725,6 +732,9 @@ def _zip_results_returns_dict(self, results, is_rescheduled) -> dict:
725732
f" {list(missmatched)}\n {self}\n {debug_var_tip}"
726733
)
727734

735+
if renames:
736+
results = {renames.get(k, k): v for k, v in results.items()}
737+
728738
return results
729739

730740
def _zip_results_plain(self, results, is_rescheduled) -> dict:

graphtik/modifier.py

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -471,10 +471,11 @@ def keyword(
471471
name: str, keyword: str = None, accessor: Accessor = None, jsonp=None
472472
) -> _Modifier:
473473
"""
474-
Annotate a :term:`needs` that (optionally) maps `inputs` name --> *keyword* argument name.
474+
Annotate a :term:`dependency` that maps to a different name in the underlying function.
475475
476-
The value of a *keyword* dependency is passed in as *keyword argument*
477-
to the underlying function.
476+
- The value of a *keyword* `needs` dependency is passed in as *keyword argument*
477+
to the underlying function.
478+
- For *keyword* to work in `provides`, the operation must be a :term:`returns dictionary`.
478479
479480
:param keyword:
480481
The argument-name corresponding to this named-input.
@@ -507,30 +508,50 @@ def keyword(
507508
508509
**Example:**
509510
510-
In case the name of the function arguments is different from the name in the
511-
`inputs` (or just because the name in the `inputs` is not a valid argument-name),
511+
In case the name of a function input argument is different from the name in the
512+
:term:`graph` (or just because the name in the `inputs` is not a valid argument-name),
512513
you may *map* it with the 2nd argument of :func:`.keyword`:
513514
514515
>>> from graphtik import operation, compose, keyword
515516
516-
>>> @operation(needs=['a', keyword("name-in-inputs", "b")], provides="sum")
517-
... def myadd(a, *, b):
518-
... return a + b
519-
>>> myadd
520-
FnOp(name='myadd',
521-
needs=['a', 'name-in-inputs'(>'b')],
522-
provides=['sum'],
523-
fn='myadd')
517+
>>> @operation(needs=[keyword("name-in-inputs", "fn_name")], provides="result")
518+
... def foo(*, fn_name): # it works also with non-positional args
519+
... return fn_name
520+
>>> foo
521+
FnOp(name='foo',
522+
needs=['name-in-inputs'(>'fn_name')],
523+
provides=['result'],
524+
fn='foo')
524525
525-
>>> graph = compose('mygraph', myadd)
526-
>>> graph
527-
Pipeline('mygraph', needs=['a', 'name-in-inputs'], provides=['sum'], x1 ops: myadd)
526+
>>> pipe = compose('map a need', foo)
527+
>>> pipe
528+
Pipeline('map a need', needs=['name-in-inputs'], provides=['result'], x1 ops: foo)
528529
529-
>>> sol = graph.compute({"a": 5, "name-in-inputs": 4})['sum']
530-
>>> sol
531-
9
530+
>>> sol = pipe.compute({"name-in-inputs": 4})
531+
>>> sol['result']
532+
4
532533
533534
.. graphtik::
535+
536+
You can do the same thing to the results of a :term:`returns dictionary` operation:
537+
538+
>>> op = operation(lambda: {"fn key": 1},
539+
... name="renaming `provides` with a `keyword`",
540+
... provides=keyword("graph key", "fn key"),
541+
... returns_dict=True)
542+
>>> op
543+
FnOp(name='renaming `provides` with a `keyword`',
544+
provides=['graph key'(>'fn key')],
545+
fn{}='<lambda>')
546+
547+
.. graphtik::
548+
549+
.. hint::
550+
Mapping `provides` names wouldn't make sense for regular operations, since
551+
these are defined arbitrarily at the operation level.
552+
OTOH, the result names of :term:`returns dictionary` operation are decided
553+
by the underlying function, which may lie beyond the control of the user
554+
(e.g. from a 3rd-party object).
534555
"""
535556
# Must pass a truthy `keyword` bc cstor cannot not know its keyword.
536557
return _modifier(name, keyword=keyword or name, accessor=accessor, jsonp=jsonp)

test/test_op.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,18 @@ def test_returns_dict_extra():
180180
assert len(res) == 2 # check it did not mutate results
181181

182182

183+
def test_returns_dict_keyword_renames():
184+
res = {"1": 1, "2": 2}
185+
op = operation(lambda: res, provides=keyword("11", "1"), returns_dict=True)
186+
assert op.compute({}) == {"11": 1}
187+
assert len(res) == 2 # check it did not mutate results
188+
189+
res = {"1": 1, "11": 11}
190+
op = operation(lambda: res, provides=keyword("11", "1"), returns_dict=True)
191+
assert op.compute({}) == {"11": 1} # original '11' was discarded
192+
assert len(res) == 2 # check it did not mutate results
193+
194+
183195
@pytest.fixture(params=[None, ["a", "b"]])
184196
def asked_outputs(request):
185197
return request.param

0 commit comments

Comments
 (0)