Contexts
Context
The context defines how a reference (f.A
, f['A']
, f.A.B
is evaluated)
Context.EVAL
This context evaluates the references directly. For example, if data is
{"a": 1}
, f["a"]
will be evaluted into 1
.
from pipda import register_verb, Context
@register_verb(dict, context=Context.EVAL)
def mutate(data, **kwargs):
data = data.copy()
data.update(kwargs)
return data
{"a": 1} >> mutate(b=f["a"] * 2) # {"a": 1, "b": 2}
Context.SELECT
This context evaluates the references into the attribute names or subscripts
themselves. This, for f.key
works sometimes as a shortcut for "key"
.
@register_verb(dict, context=Context.SELECT)
def select(data, *keys):
return {key: val for key, val in data.items() if key in keys}
{"a": 1, "b": 2} >> select(f.a) # {"a": 1}
Context.PENDING
This remains expressions un-evaluated so that they get passed into the function and evaluted later inside the function.
from pipda import Context, register_verb, evaluate_expr
@register_verb(dict, context=Context.PENDING)
def mutate(data, **kwargs):
# kwargs is holding expressions
kwargs = evaluate_expr(kwargs, data, Context.EVAL)
data = data.copy()
data.update(kwargs)
return data
{"a": 1} >> mutate(b=f["a"] * 2) # {"a": 1, "b": 2}
Customizing a context
You can write your own context by subclassing ContextBase
. Overwrite the
following methods to define the behaviors:
getattr
: How to evaluatef.A
getitem
: How to evaluatef["A"]
ref
(property): How to evaluatex
inf[x]
from pipda import Context, ContextBase, register_verb
class MyContext(ContextBase):
def getattr(self, parent, ref, level):
# f.A -> level 1
# f.A.B -> level 2
return parent[ref]
def getitem(self, parent, ref, level):
return ref
@register_verb(dict, context=MyContext())
def subset_and_update(data, *cols, **kwargs):
data = {key: val for key, val in data.items() if key in cols}
data.update(kwargs)
return data
{"a": 1, "b": 2, "c": 3} >> subset_and_update(f.a, f.b, a=f.c)
# {"a": 3, "b": 2}
Note
Note that for Context.PENDING
, we need to subclass context.ContextPending
to keep expressions unevaluated.
Contexts for keyword arguments
We can set extra contexts for keyword arguments.
@register_verb(
dict,
context=Context.EVAL,
kw_context={"cols": Context.SELECT},
)
def subset_and_update(data, *, cols, **kwargs):
data = {key: val for key, val in data.items() if key in cols}
data.update(kwargs)
return data
{"a": 1, "b": 2, "c": 3} >> subset_and_update(f.a, f.b, a=f.c)
# {"a": 3, "b": 2}
Note
By default, the context is None
, the expressions, including FunctionCall
and VerbCall
objects, will be awaiting next coming avaiable context to
evaluate
Note
When registering another type(s) for a verb, contexts and extra contexts
are inherited for the first ones that registered with register_verb
Tip
Each implementation for the registered verbs or functions can have its own contexts. Specify them when registering the implementations.
<verb>.register(..., context=..., kw_context=...)
<func>.register(..., context=..., kw_context=...)