POP Release 20.0.0

We are pleased to release POP 20.0.0. This release solidifies the use of dynamic or “reverse” subs.

Reverse Subs

A reverse sub is a placeholder for a resource that has yet to be discovered. It is useful when you want to call an external API with contracts and subs, but don’t know what is available at initialization.

In addition to the traditional arguments taken by “hub.pop.sub.add” or “Sub.__init__”, ReverseSub needs a “resolver”.

The ReverseSub object takes the same initialization arguments as a Sub and adds three new arguments:

  • resolver – a callable function that returns another callable, see below for details
  • context – any generally useful object that needs to be used with the resolver function and needs a place to stay
  • refs – This argument is used by the ReverseSub when it creates child ReverseSubs to keep track of attribute references.

hub.pop.sub.dynamic

This is the function on the hub that should be used to add a ReverseSub to another sub. It’s arguments are the same as those of hub.pop.sub.add in addition to the “resolver” and context” arguments that the ReverseSub needs.

Resolver function

The resolver is a function that interprets the ReverseSub’s reference and returns the appropriate caller to the external code. The user defined resolver function must take two arguments, “ref” and “context” and returns a callable.

Note

If the resolver function is on the hub then it also has “hub” as it’s first argument

“ref” will be a period delimited string of the dynamic namespaces that led up to a call. For example, if I have a ReverseSub on hub.exec.gcp and my code calls hub.exec.gcp.foo.bar.baz(), “ref” will be “gcp.foo.bar.baz” in the resolver function.

Examples

This is the most basic example of how to use the resolver function and a ReverseSub

import pop.hub

hub = pop.hub.Hub()
hub.pop.sub.add(dyne_name="my_dyne")

context = "An object that will be universally useful for resolving references under the ReverseSub"


def _resolver(ref: str, context):
    # return a callable based on the reference and context
    return lambda *a, **kw: (a, kw, ref, 1)


hub.pop.sub.dynamic(
    sub=hub.my_dyne, subname="my_sub", resolver=_resolver, context=context
)

ret = hub.my_dyne.my_sub.foo.bar.baz("arg1", kwarg="value1")
assert ret == (("arg1",), {"kwarg": "value1"}, "my_sub.foo.bar.baz", 1)

Here is an example using a ReverseSub under idem’s exec sub called “gcp”:

# project_root/my_project/tool/gcp/init.py


def __init__(hub):
    context = "An object that will be universally useful for resolving references under the ReverseSub"
    hub.pop.sub.dynamic(
        sub=hub.exec,
        subname="gcp",
        resolver=hub.tool.gcp.init.resolver,
        context=context,
    )


def resolver(hub, ref: str, context):
    return lambda: "return a callable based on the reference and context"