Skip to main content

According to safe: Tthe user parameters behave a little bit differently inside a custom transformer. They are in fact prefixed with the name of the custom transformer to avoid collisions with the main workspace parameters. Example: if you have a custom transformer called "custtransf" with a published parameter called "custparam", you will have to refer to it like this.

value = FME_MacroValues["custtransf_custparam"] The issue is that a user can change the name of the custom transformer in the main canvas, or could have multiple copies ("custtransd_2") and the reference breaks. Ideally I would like to just refer to the user parameter without prefixing anything, and have FME figure it out when interpreting the python. If that's not possible, then having the ability to insert a "transformer name" into the python call would also work. value = FME_MacroValues[transformerName + "_custparam"]

I agree this is a hassle, would be nice if we could get an elegant solution in the API that does not require a ParameterFetcher.


The current behavior breaks encapsulation.


I agree. At the moment I am strictly using parameter fetchers within custom transformers to bypass this issue. However, this creates additional attributes that I need to clean up (which might interfere with input attributes).


This is a big problem for current development work in my organisation. I have an Esri Geodatabase Writer inside a custom transformer and I need to define the values of the Feature Operation and Truncate Existing parameters using Python. I need to be able to define the Feature Operation and Truncate Existing parameters based on the value of a Published Parameter, but as stated above, when using a Scripted Private Parameter I cannot access the value of the Published Parameter value because the name of the Published Parameter has been changed by FME. The ParameterFetcher work-around will not work because these settings in the Writer cannot be linked to an Attribute Value.


This makes PythonCaller extremely brittle and unreliable when used inside custom transformers (where they seem to be very common), and affects not only published parameter use, but also feature type matching (since features created inside a custom transformer are also prefixed by instance), which makes it harder to deal with PythonCaller's lack of additionnal input ports.


It’s not especially clean, but there is a way of handling this quirk programatically:
 

import fmeobjects

ACTUAL_TRANSFORMER_NAME = "MyPythonCaller"

class FeatureCreator(object):
def __init__(self):
pass

def get_local_parameter(self, user_param_name):
assert hasattr(self, "factory_name"), "factory_name does not exist during __init__()"

if not hasattr(self, "scope_name"):
# factory_name: MyCustomTransformerInstanceName_MyPythonCaller
scope_prefix = self.factory_name.removesuffix(ACTUAL_TRANSFORMER_NAME)

if scope_prefix != self.factory_name and scope_prefix.endswith("_"):
self.scope_name = scope_prefix.removesuffix("_") # MyCustomTransformerInstanceName
else:
self.scope_name = None

if self.scope_name:
local_param_name = f'{self.scope_name}_{user_param_name}'
else:
local_param_name = user_param_name

local_param_value = FME_MacroValuesulocal_param_name]
print(f"{local_param_name}: {local_param_value}")
return local_param_value



def input(self, feature):
self.get_local_parameter("MY_USER_PARAM")
self.get_local_parameter("OTHER_USER_PARAM")

newFeature = fmeobjects.FMEFeature()
self.pyoutput(newFeature)

def close(self):
pass

I’m not sure in which FME release self.factory_name was added, but its at least there in FME 2023 and 2022.

As highlighted by the assertion at the start, that doesn’t work inside __init__(), and you need to make sure ACTUAL_TRANSFORMER_NAME always matches the Transformer Name of the PythonCaller/PythonCreator that code exists within.

I would still prefer a properly supported way of handling this, but this looks like a reasonably robust self-contained workaround (as opposed to using an upstream ParameterFetcher or an AttributeCreator to add the parameters to all features).


I consider it best practice to not refer to parameters outside the CT, any data/values to be passed in should go through custom transformer parameters or attributes.


I consider it best practice to not refer to parameters outside the CT, any data/values to be passed in should go through custom transformer parameters or attributes.

I believe you misunderstand: all Custom Transformer parameters in FME_MacroValues are prefixed with the instance name of the transformer, which is fine on its own (even tough, like you said, using parameters from outside the CT is kind of an anti-pattern), but you have no documented mean of knowing what prefix your PythonCaller is running out of, and therefore no way of reliably getting these parameters from Python. What I suggested above works, but it’s definitely a hack and I certainly wouldn’t expect authors of custom transformers to have to use it all the time.


Hi everyone,

I would like to propose my solution that works perfectly.

In your PythonCaller inside your custom transformer, use this.

 

In the __init__ function, get the parameter’s name with :

# Get the transformer instance name
for key, value in fme.macroValues.items():
if key.endswith('_WORKSPACE_NAME'):
self.customTransformerInstanceName = value
break

# Define the parameter name
self.userParameter__myParameter = self.customTransformerInstanceName + '_' + 'myParameterName'

 

You can then use this to call the parameter’s value :

fme.macroValuesVself.userParameter__myParameter]

 

Here is a complete example :

import fmeobjects
import fme

class FeatureProcessor(object):

def __init__(self):

# Define a logger for testing
self.logger = fmeobjects.FMELogFile()

# Get the transformer instance name
for key, value in fme.macroValues.items():
if key.endswith('_WORKSPACE_NAME'):
self.customTransformerInstanceName = value
break

# Define the parameter name
self.userParameter__myParameter = self.customTransformerInstanceName + '_' + 'myParameterName'

def input(self, feature: fmeobjects.FMEFeature):

self.logger.logMessageString('My parameter has this value : ' + fme.macroValuesoself.userParameter__myParameter], fmeobjects.FME_INFORM)

self.pyoutput(feature, output_tag="PYOUTPUT")

def close(self):
pass