Hi, we are looking at writing a custom component a...
# getting-started
a
Hi, we are looking at writing a custom component and the documentation is incomplete as to what should be supplied to the “props” of the component base class. Going with Python:
Copy code
super().__init__(
  "<component-type-id>",
  "<component-name>",
  props: Optional[Inputs],
  opts
)
In the examples I can find the
props
are not handled. Digging through the code that seems to resolve to the
Resource.__init__(... props )
To confirm, my best guess is that any
pulumi.Input[]
to my custom component should be forwarded to the base class
__init__
AND that part of the reason for this is to allow Pulumi to work out dependency ordering between my component and other resource outputs… BUT that it generally doesn’t matter in practice because these are forwarded to sub-resources of the component so the dependency ordering is handled that way. --- So the question here: (1) is the understanding above correct and (2) are there any other important reasons for specifying the component properties? In all of the Pulumi examples, they seem to pass
None
or
{}
(e.g. Azure Virtual Data Center)
d
@quiet-wolf-18467 as he has been building a few of these
q
@acoustic-evening-77298 If you'd like to chat and work through this, please reach out
a
Ok - so to answer my own question for the next person. So the
args
bit is somewhat overloaded under the hood and provides 2 functions: 1. It automatically sets resource inputs as outputs on the resource (see below) 2. In the context of DynamicProviders, you need to specify an empty/None/Null/Nil value in the Map to let the infrastructure to know to copy outputs back into the Resource. This seems to contradict what I found in the TypeScript example so I am not sure if this is a Python specific defect or a defect in the example (@quiet-wolf-18467) ? For a Component, you can just set the output in the component constructor since there is less pulumi magic in the backend. ---- (1) Example pass through:
Copy code
@dataclasses.dataclass
class MyArgs:
    foo: Input[str]
    bar: Input[str]

class MyComponent(ComponentResource):
    # Auto populated by Pulumi. These fields here are handy for mypy / IDE typeahed
    foo: Output[str]
    bar: Output[str]
    myout: Output[str]  # declared here for consistency

    def __init__(self, name: str, args: MyArgs, opts: Optional[ResourceOptions] = None)
        super().__init__(
            "what:ever:MyComponent",
            name=name,
            props=dataclasses.asdict(args),   # This populates foo, bar as output properties on the Component resource
            opts=opts
        )

        self.myout = <whatever>
--- (2) In the context of Dynamic Resources:
Copy code
# NOTE BaseModel does not play nice with deferred/Input/Output. Use dataclasses instead there
# At this level or the provider inputs are resolved to concrete types
class MyProviderIn(pydantic.BaseModel): 
    foo: str
    bar: str

class MyProviderOut(MyProviderIn):  # extend above with extra output
    myout: str

class MyDynamicProvider(dynamic.ResourceProvider):
    ...

    def create(self, props: Dict):
        props_in = MyProviderIn(**props)

        .. # do stuff

        return CreateResult(id, MyProviderOut(myout=<result>, **props_in.dict()) 

@dataclasses.dataclass
class MyArgs:
    foo: Input[str]
    bar: Input[str]

class MyDynamicResource(dynamic.Resource):
    foo: Output[str]
    bar: Output[str]
    myout: Output[str]

    def __init__(self, name: str, props: MyDynamicResourceArgs, opts: Optional[ResourceOptions] = None):
        props_dict_with_outputs_also = dataclasses.asdict(props)

        # GOTCHA / POTENTIAL BUG: if you do not set this, you cannot access `myout` later in your __main__
        # Even though it is set by your provider
        props_dict_with_outputs_also["myout"] = None

        super().__init__(MyDynamicProvider(), name, props_dict_with_outputs_also, opts):
Later this finally works
Copy code
# __main__

my_component = MyComponent("whatever", MyArgs(foo="foo", bar="bar")
my_component_out: Output[str] = my_component.myout  # This works as expected

my_dynamic_res = MyDynamicResouce("whatever", MyArgs(foo="foo", bar="bar")
my_dynamic_res_out: Output[str] = my_dynamic_res.myout  # This will blow up without the gotcha fix above
``````