Question

putting listAppender inside custom transformer throws python error

  • 7 January 2019
  • 6 replies
  • 3 views

Badge +11

Hi @sander!

I'm using your ListAppender inside a custom transformer and it's throwing python error. This is the message:

INCLUDE -- failed to evaluate Python script `def ParamFunc():
  return FME_MacroValues[FME_MacroValues['ListAppender_WORKSPACE_NAME'] + '_' + 'APPEND_VALUES']
value = ParamFunc()
macroName = FME_MacroValues['ListAppender_WORKSPACE_NAME'] + '_APPEND_VALUES_WWJD'
if value == None:
  return { macroName : u'' }
else:
  import six
  try:
  Â  value = six.text_type(value)
  except UnicodeDecodeError:
  Â  value = six.text_type(value, 'utf-8')
  return { macroName : value }
'
Program Terminating
Translation FAILED.
Traceback (most recent call last):
  File "<string>", line 5, in MF_Include_1546897172100
  File "<string>", line 4, in ParamFunc
KeyError: u'$(test_WORKSPACE_NAME)_ListAppender_10_APPEND_VALUES'

No issues when ListAppender is on the main canvas.


6 replies

Userlevel 5

Looks like the Python code wasn't written to account for the particularities of running inside a custom transformer. I'm fairly sure @sander can fix it when he gets the time.

In the meantime, the workaround is to not use the ListAppender in a custom transformer, as you've also mentioned.

If you want to have a go at it yourself, there is some technical discussion about how to do this here:

https://knowledge.safe.com/questions/44873/getting-published-parameters-from-within-a-python.html

Badge +7

Hi @tnarladni!

Sorry to hear that you had this issue and thanks for reporting it. I have actually never tested any of my FME Hub transformers for use inside another custom transformer, so I've got some work to do ;)

As @david_r already mentioned (thanks for the mention btw - strangely, I didn't receive a notification for the original post), his proposed solution could do the trick. Although I'd wish that Safe would come up with a better/easier solution, tbh.

Sander

Badge +22

Hi @tnarladni!

Sorry to hear that you had this issue and thanks for reporting it. I have actually never tested any of my FME Hub transformers for use inside another custom transformer, so I've got some work to do ;)

As @david_r already mentioned (thanks for the mention btw - strangely, I didn't receive a notification for the original post), his proposed solution could do the trick. Although I'd wish that Safe would come up with a better/easier solution, tbh.

Sander

My workaround is to use a parameterFetcher just before the pythonCaller, and then in the python code get those attributes instead of the original parameters.

Userlevel 5

My workaround is to use a parameterFetcher just before the pythonCaller, and then in the python code get those attributes instead of the original parameters.

Yeah, I've started doing the same thing. Basically it's the only solution that's guaranteed to work on every version and that doesn't rely on undocumented behavior.

Badge +7

My workaround is to use a parameterFetcher just before the pythonCaller, and then in the python code get those attributes instead of the original parameters.

Well, surprise... I did exactly that! :)

I looked into it, and the error that @tnarladni pasted above is actually thrown by a scripted parameter in the ListAppender. It is not caused by the PythonCaller inside the transformer.

The ListAppender has a published parameter called APPEND_VALUES (which can take a list of values), but since I want the user to be able to use special characters and spaces, I'd like the value of this parameter to be WWJD encoded (using XML-like tags, where e.g. a space is replaced by <space>). An easy way of doing that (I actually haven't found a better solution), is by making a private Python-scripted parameter and to have it return the public parameter immediately, like so:

return FME_MacroValues['APPEND_VALUES']

This works fine, but the published parameter cannot be found anymore when the ListAppender is nested inside another custom transformer. As discussed in the technical question linked by @david_r in the comments here, it is hard to obtain the full name of the parameter.

The good news is that I have found a workaround that actually works pretty well, no matter how deep the ListAppender has been nested inside other custom transformers. It also works regardless of the name (e.g. when ListAppender is renamed to ListAppender2). In order to work, the code inside the private parameter APPEND_VALUES_WWJD of the ListAppender should become:

context = FME_MacroValues.get('WB_CURRENT_CONTEXT')
for key, value in FME_MacroValues.items():
    if key.endswith('APPEND_VALUES'):
        if not context or (context and key.startswith(context)):
            return value
return ''

I will apply this asap in all my affected FME Hub transformers.

Of course, if you'd like to use this in a PythonCaller directly, it would make sense to parameterize the code and wrap it inside its own function, for example:

def get_macro_value(param_name, default=None):
    context = FME_MacroValues.get('WB_CURRENT_CONTEXT')
    for key, value in FME_MacroValues.items():
        if key.endswith(param_name):
            if not context or (context and key.startswith(context)):
                return value
    return default

Like this, you can call get_macro_value() anywhere in a custom transformer PythonCaller and always get the desired value. When it's not found, default is returned (None if not specified).

In my case, I would call get_macro_value('APPEND_VALUES', ''). This will get the values from the published parameter APPEND_VALUES of the custom transformer and if that is not found, an empty string is returned.

Hope this is clear...

 

Edit: the above is still a workaround and caution is advised! For example, if you have another published parameter called MORE_APPEND_VALUES, this value could be returned by the code above instead, depending on which key comes first in the dictionary iteration (and for Python dictionaries, this is pretty random). Furthermore, the parameter names are case sensitive.

Userlevel 5

Well, surprise... I did exactly that! :)

I looked into it, and the error that @tnarladni pasted above is actually thrown by a scripted parameter in the ListAppender. It is not caused by the PythonCaller inside the transformer.

The ListAppender has a published parameter called APPEND_VALUES (which can take a list of values), but since I want the user to be able to use special characters and spaces, I'd like the value of this parameter to be WWJD encoded (using XML-like tags, where e.g. a space is replaced by <space>). An easy way of doing that (I actually haven't found a better solution), is by making a private Python-scripted parameter and to have it return the public parameter immediately, like so:

return FME_MacroValues['APPEND_VALUES']

This works fine, but the published parameter cannot be found anymore when the ListAppender is nested inside another custom transformer. As discussed in the technical question linked by @david_r in the comments here, it is hard to obtain the full name of the parameter.

The good news is that I have found a workaround that actually works pretty well, no matter how deep the ListAppender has been nested inside other custom transformers. It also works regardless of the name (e.g. when ListAppender is renamed to ListAppender2). In order to work, the code inside the private parameter APPEND_VALUES_WWJD of the ListAppender should become:

context = FME_MacroValues.get('WB_CURRENT_CONTEXT')
for key, value in FME_MacroValues.items():
    if key.endswith('APPEND_VALUES'):
        if not context or (context and key.startswith(context)):
            return value
return ''

I will apply this asap in all my affected FME Hub transformers.

Of course, if you'd like to use this in a PythonCaller directly, it would make sense to parameterize the code and wrap it inside its own function, for example:

def get_macro_value(param_name, default=None):
    context = FME_MacroValues.get('WB_CURRENT_CONTEXT')
    for key, value in FME_MacroValues.items():
        if key.endswith(param_name):
            if not context or (context and key.startswith(context)):
                return value
    return default

Like this, you can call get_macro_value() anywhere in a custom transformer PythonCaller and always get the desired value. When it's not found, default is returned (None if not specified).

In my case, I would call get_macro_value('APPEND_VALUES', ''). This will get the values from the published parameter APPEND_VALUES of the custom transformer and if that is not found, an empty string is returned.

Hope this is clear...

 

Edit: the above is still a workaround and caution is advised! For example, if you have another published parameter called MORE_APPEND_VALUES, this value could be returned by the code above instead, depending on which key comes first in the dictionary iteration (and for Python dictionaries, this is pretty random). Furthermore, the parameter names are case sensitive.

That's a really nice solution Sander, thanks for sharing! :-)

Reply