Solved

Python Custom Transformer Bug using fme.macroValue

  • 9 January 2019
  • 3 replies
  • 25 views

Userlevel 3
Badge +17

I'm trying to write a custom transformer with a python script that looks at the Public Parameter.

There is a lot of trial and error going on but I think I'm doing it as it should now.

 

This helped me getting started:

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

 

The transformer works if the Public Parameter is set to a fixed value or if it is set to a feature attribute value. But when there are two instances of the Custom Transformer it looks at whatever starts first. So the second transformer uses the fixed value of the first transformer.

 

custom_transformer_name = 'MultiInstanceBug'
parameter_name = 'codeLength'
instance_name = FME_MacroValues[custom_transformer_name + '_WORKSPACE_NAME']
mv_codeLength = FME_MacroValues[instance_name + '_' + parameter_name]

if mv_codeLength[0:6] == '@Value':
  codeLength = int(feature.performFunction(mv_codeLength))
elif mv_codeLength[0:6] == '@Evalu':
  codeLength = int(feature.performFunction(mv_codeLength))
elif mv_codeLength != '':
  codeLength = int(mv_codeLength)
else:
  codeLength = 0

logger.logMessageString('codeLength: ' + str(codeLength),fmeobjects.FME_INFORM)

 

This gives unexpected results, the only workaround I now see is using an AttributeCreator and AttributeRemover to write the value to the feature first.

 

See the workbench I created: 

 

 

icon

Best answer by geosander 13 January 2019, 15:13

View original

3 replies

Userlevel 4

This is a known issue / challenge / problem (depending on how you look at it). Your workaround using e.g. an AttributeCreator + AttributeRemover is currently the simplest and most robust solution, so that's what I'd recommend.

Badge +7

Bit late probably, but have you tried out my workaround (as discussed here)? It might do the trick fetching the right macro value, but you will have to export/link your custom transformer as an FMX.

In your case, you could try adding the following function at the "global" level of your Python code:

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

Then, you can replace the first 4 lines in your code above with:

parameter_name = 'codeLength'
mv_codeLength = get_macro_value(parameter_name)

Specifying the custom transformer name is irrelevant here, since the function fetches that from the WB_CURRENT_CONTEXT macro value, which always exists within a custom transformer and returns the current name of the transformer. This is why it only works for linked custom transformers: WB_CURRENT_CONTEXT is empty for embedded transformers.

Let me know if that works out for you!

 

Btw, I would replace lines 6-13 in your code with (to make it a little more robust):

codeLength = 0
try: 
    if mv_codeLength.startswith(('@Value', '@Evaluate')):
        codeLength = int(feature.performFunction(mv_codeLength))
    elif mv_codeLength.isdigit():
        codeLength = int(mv_codeLength)
except (TypeError, AttributeError, ValueError) as e:
    # Can happen when mv_codeLength is not a string or 
    # when the feature function output can not be cast to an integer.
    # You could log this error or do something else here.
    pass

 

Finally, there actually is a way to fetch the name of the current custom transformer name from within (even if it is embedded), but you will have to use a PythonCaller with a FeatureProcessor class. When instantiated, a property called self.factory_name is set, which starts with the name of the custom transformer, followed by the name of the PythonCaller (and concatenated by an underscore)!

Userlevel 3
Badge +17

Bit late probably, but have you tried out my workaround (as discussed here)? It might do the trick fetching the right macro value, but you will have to export/link your custom transformer as an FMX.

In your case, you could try adding the following function at the "global" level of your Python code:

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

Then, you can replace the first 4 lines in your code above with:

parameter_name = 'codeLength'
mv_codeLength = get_macro_value(parameter_name)

Specifying the custom transformer name is irrelevant here, since the function fetches that from the WB_CURRENT_CONTEXT macro value, which always exists within a custom transformer and returns the current name of the transformer. This is why it only works for linked custom transformers: WB_CURRENT_CONTEXT is empty for embedded transformers.

Let me know if that works out for you!

 

Btw, I would replace lines 6-13 in your code with (to make it a little more robust):

codeLength = 0
try: 
    if mv_codeLength.startswith(('@Value', '@Evaluate')):
        codeLength = int(feature.performFunction(mv_codeLength))
    elif mv_codeLength.isdigit():
        codeLength = int(mv_codeLength)
except (TypeError, AttributeError, ValueError) as e:
    # Can happen when mv_codeLength is not a string or 
    # when the feature function output can not be cast to an integer.
    # You could log this error or do something else here.
    pass

 

Finally, there actually is a way to fetch the name of the current custom transformer name from within (even if it is embedded), but you will have to use a PythonCaller with a FeatureProcessor class. When instantiated, a property called self.factory_name is set, which starts with the name of the custom transformer, followed by the name of the PythonCaller (and concatenated by an underscore)!

Thanks, lot's of good comments on my code. The code is indeed enclosed in a Python Class that is called by the PythonCaller. Don't know if I think it is worth it to make the code only work Linked not Embedded.

Reply