[OD Functions] Pass JSON argument to set production line variables

Hi,

I have created the following One Data function that sets the input arguments of a Production line to be executed:

import requests
import json

def handle(req):
    """
    triggers the PL that is defined in the
    environments and endpoints input table
    :input:
    - body, JSON-encoded parameters for the production line
    - pl_id (str), ID of the production line
    """
    
    api_endpoint = req["config"]["onedataBaseurl"]+ "/api/v1"
    x_auth_token =  req["config"]["authorization"]
    
    body = req["args"]["body"]
    pl_id = req["args"]["pl_id"]
    

    #Set PL variables
    url = api_endpoint + '/production-lines/' + pl_id
    header = {'X-Auth-Token' : x_auth_token, 'Content-Type' : 'application/json'}
    try:
        put_response = requests.put(url=url, data=json.dumps(body), headers=header, verify=False)
    except requests.exceptions.RequestException as e:
        print("Login unsuccessful: ", e)
        #raise SystemExit(e)
    print(put_response.json())

When passing the following JSON as input to the function in the debugger mode of the function, I get the following error though.

{   
    "body" : "{\"stdout\":\"\",\"stderr\":\"\",\"response\":{\"owner\":\"a3fab4df-540c-4b26-b66e-e942006331e7\",\"id\":\"e91eac8f-35e2-403c-aa06-af9fdce26bc8\",\"name\":\"Trigger A03\.....",
    
    "pl_id" : "e91eac8f-35e2-403c-aa06-af9fdce26bc8"
}

Please note that the JSON is very length, yet it is formatted correctly, as the execution of this very same function in a plain Python processor of One Data runs just fine with the exact same input. Upon request, I can provide a link to the function on One Data internal.

{
“stdout”: errors’: [‘JSON parse error: Cannot construct instance of de.onelogic.forecasting.exchange.productionline.ProductionLineDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value (\’{“stdout”:"",“stderr”:"",“response”

nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of de.onelogic.forecasting.exchange.productionline.ProductionLineDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value

at [Source: (PushbackInputStream)

Looks like the payload to the production line endpoint is malformed.
Have a look at stdout, stderr etc, which should not be part of the payload to the production line endpoint.

{
“body” : “{\“stdout\”:\”\",\“stderr\”:\"\",\“response\”:{\“owner\”:\“a3fab4df-540c-4b26-b66e-e942006331e7\”,\“id\”:\“e91eac8f-35e2-403c-aa06-af9fdce26bc8\”,\“name\”:\“Trigger A03\…”,
“pl_id” : “e91eac8f-35e2-403c-aa06-af9fdce26bc8”
}

I think you want something like:

{ "body": { "owner": "a3fab4df-540c-4b26-b66e-e942006331e7", "id": "e91eac8f-35e2-403c-aa06-af9fdce26bc8", [all other properties of the PL here] }, "pl_id": "e91eac8f-35e2-403c-aa06-af9fdce26bc8" }

I also think the escaping of the payload is not necessary. body can be a JSON object. :slight_smile:

Thank you for the reply Tristan. Actually, by removing the print statement at the bottom of the function, the debugging mode now works with this input :sweat_smile:

Actually Tristan you were right, and I got misled to believe that my function was working properly while it was not, as I was getting a 200 response, despite the variables not being properly set in the PUT request to the production line.

→ I managed to solve the problem with two extra modifications:

  1. I removed the std and stderr fields from the dictionary, considering just the response fields to be passed within my body
  2. By returning the put_response formatted as JSON (instead of printing it), I was able to visualize what was going wrong previously, and obtain a clean output as a response.

For future reference when wanting to pass variables to a workflow being triggered, this is the code I used as One Data function:

The input accepted is the output of a GET request to the corresponding production line, filtered on the response field:

import requests
import json

def handle(req):
    """
    triggers the PL that is defined in the
    environments and endpoints input table
    :input:
    - body, JSON-encoded parameters for the production line
    - pl_id (str), ID of the production line
    """
    
    api_endpoint = req["config"]["onedataBaseurl"]+ "/api/v1"
    x_auth_token =  req["config"]["authorization"]
    
    body = req["args"]["body"]
    pl_id = req["args"]["pl_id"]
    

    #Set PL variables
    url = api_endpoint + '/production-lines/' + pl_id
    header = {'X-Auth-Token' : x_auth_token, 'Content-Type' : 'application/json'}
    try:
        put_response = requests.put(url=url, data=body, headers=header, verify=False)

    except requests.exceptions.RequestException as e:
        return("Login unsuccessful: ", e)
        #raise SystemExit(e)
    return(put_response.json())

TLDR: DO NOT use print statements as output of One Data functions, always double/triple check their input, and make sure to get a meaningful response to ensure the output of a function is indeed correct.

1 Like

So using the PUT function above, the code was indeed working to set production line variables, however that caused a lot of overhead with my workflow, which caused the workflow to takte about ~2x as much time as the version with no REST API calls and functions.

After discussion with @laura.luckert and following the suggestions received by @Flogge , I hence implemented all my REST API calls in a “single-shot” call to a function that does exactly what I need, namely: triggers a production line, allowing to pass variables to the workflow in the production line in a simple manner . The documentation for this API call is the following, and this function has allowed me to reduce my workflow’s run time from 16 seconds (because of 3 REST API calls, and parsing of their output) to about 9 seconds.

http://od-api.pages.intranet.onelogic.de/#panel_production_lines__id__run

The code is the following:

import requests
import json

def handle(req):
    """
    triggers the PL passed as input
    :input:
        - pl_id (str): the ID of the production line to be executed
    """
    
    api_endpoint = req["config"]["onedataBaseurl"]+ "/api/v1"
    x_auth_token =  req["config"]["authorization"]
    
    pl_id = req["args"]["pl_id"]
    body = req["args"]["body"]

    
    ## trigger workflow via API
    url_run = api_endpoint + '/production-lines/' + pl_id + '/run'
    header = {'X-Auth-Token' : x_auth_token, 'Content-Type' : 'application/json'}
    
    try:
        post_response = requests.post(url=url_run, data=body,  headers=header, verify=False)
        #message = "PL was triggered successfully"
        #od_output.add_data("output", df)
    except requests.exceptions.RequestException as e:
        print("Trigger unsuccessful. Exit with error code: ", post_response.status_code)
    return(post_response.json())