12

My orchestrator receives a payload, with that payload it contains instructions that need to be passed along with other sets of data to activity functions.

how do I pass multiple parameters to an activity function? Or do I have to mash all my data together?

def orchestrator_function(context: df.DurableOrchestrationContext):
    
    # User defined configuration
    instructions: str = context.get_input()

    task_batch = yield context.call_activity("get_tasks", None)
    
    # Need to pass in instructions too
    parallel_tasks = [context.call_activity("perform_task", task) for task in task_batch]

    results = yield context.task_all(parallel_tasks)
    
    return results

The perform_task activity needs both the items from task_batch and the user input instructions

Do I do something in my function.json?

Workaround Not ideal, but I can pass multiple parameters as a single Tuple

something = yield context.call_activity("activity", ("param_1", "param_2"))

I then just need to reference the correct index of the parameter in the activity.

Ari
  • 5,301
  • 8
  • 46
  • 120

5 Answers5

14

Seems there's no text-book way to do it. I have opted to give my single parameter a generic name like parameter or payload.

Then when passing in the value in the orchestrator I do it like so:

payload = {"value_1": some_var, "value_2": another_var}
something = yield context.call_activity("activity", payload)

then within the activity function, I unpack it again.

edit: Some buried documentation seems to show that https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-error-handling?tabs=python

Ari
  • 5,301
  • 8
  • 46
  • 120
9

Just to add to @Ari's great answer, here code to pass data from client function (HTTP request in this case) all the way to activity function:

Client -> Orchestrator -> Activity

Client

async def main(req: func.HttpRequest, starter: str) -> func.HttpResponse:
    client = df.DurableOrchestrationClient(starter)

    req_data = req.get_json()
    img_url = req_data['img_url']
    payload = {"img_url": img_url}
    
    instance_id = await client.start_new(req.route_params["functionName"], None, payload)

    logging.info(f"Started orchestration with ID = '{instance_id}'.")

    return client.create_check_status_response(req, instance_id)

Orchestrator

def orchestrator_function(context: df.DurableOrchestrationContext):
    input_context = context.get_input()
    img_url = input_context.get('img_url')

    some_response= yield context.call_activity('MyActivity', img_url)
    
    return [some_response]

Activity

def main(imgUrl: str) -> str:
    print(f'.... Image URL = {imgUrl}')

    return imgUrl
gary
  • 425
  • 5
  • 20
1

You can use @dataclass and @dataclass_json class decorators for your input and output types, like this:

@dataclass_json
@dataclass
class Command:
  param1: str
  param2: int


@dataclass_json
@dataclass
class Result:
  val1: str
  val2: int

and then you can use those in Azure Functions, e.g. in Activity ones:

def main(input: DownloadFileRequest) -> DownloadFileResponse:
  # function code
  result: DownloadFileResponse = DownloadFileResponse("some", 123)
  return result

This provides you with a clean API and descriptive code. Much better approach than using dictionaries, at least for me.

Kokos
  • 2,143
  • 3
  • 17
  • 16
1

I would also suggest the dataclass-wizard as a viable option, and one which should also be a bit more lightweight alternative to dataclasses-json; it is lighter in the sense that it does not use external libraries like marshmallow for generating schemas. It also performs a little better FWIW anyway.

I didn't really understand the schema of the data as outlined in the question unfortunately, thus I decided to roll my own for the purposes of a quick and dirty demo, so check it out:

from dataclasses import dataclass
from dataclass_wizard import JSONWizard

@dataclass
class Batman(JSONWizard):
  suit_color: str
  side: 'Sidekick'


@dataclass
class Sidekick:
  name: str
  mood: str

# usage
bats = Batman.from_dict({'suitColor': 'BLACK',
                         'side': {'Name': 'Robin', 'mood': 'Mildly depressed'}})

print(repr(bats)) 
# prints:
#   Batman(suit_color='BLACK', side=Sidekick(name='Robin', mood='Mildly depressed'))
print(bats.to_dict())
# prints:
#   {'suitColor': 'BLACK', 'side': {'name': 'Robin', 'mood': 'Mildly depressed'}}
rv.kvetch
  • 9,940
  • 3
  • 24
  • 53
0

I have never used Azure functions in Python, only in C#, but I am assuming that in addition to passing the values as a dict, you could also pass them as a tuple that can be restructured within the function.

Michael Murphy
  • 1,921
  • 2
  • 18
  • 21