Skip to main content
Released

Add Rejected-port to the PythonCaller

Related products:Transformers

david_r
Evangelist

It would be very helpful to have a Rejected-port on the PythonCaller.

Outputting a failed feature could e.g. look like this:

self.pyoutput_failed(feature)

This would simplify the pattern where you need to set a temporary attribute to indicate success/failure, then a Tester and then removing the temporary attribute.

This post is closed to further activity.
It may be a question with a best answer, an implemented idea, or just a post needing no comment.
If you have a follow-up or related question, please post a new question or idea.
If there is a genuine update to be made, please contact us and request that the post is reopened.

19 replies

takashi
Contributor
Forum|alt.badge.img+23
  • Contributor
  • March 22, 2016

I like it. It might be ideal if the user could add (and rename) output ports optionally. How about this?

self.pyoutput(feature) # to the topmost port self.pyoutput(feature, 'port_name') # to an additional port

david_r
Evangelist
  • Author
  • March 22, 2016

Even better :-)


geosander
Forum|alt.badge.img+7
  • March 22, 2016

Yes, that would be really great! Filtering on the spot. I now always tag my features with some attribute and use a TestFilter afterwards or something.


jdh
Contributor
Forum|alt.badge.img+28
  • Contributor
  • March 22, 2016

That is a great idea, I think 90% of the time my pythonCaller is immediately followed by a tester/TestFilter to separate the results.


ekkischeffler
Contributor
Forum|alt.badge.img+5

How about an additional port for a failing Python script instead of making the whole workspace fail?


Forum|alt.badge.img
  • November 10, 2016
Yes please - this would be an enormous improvement :)

 

 


Forum|alt.badge.img
  • January 23, 2018

It would be great if this port included a Line Number, Type Name and settable Context attribute, e.g.

import sys ... context = '' try: parsed_json = json.loads('{...}') #some valid JSON for k, v in parsed_json.items(): context = k # do stuff except Exception as e: error = 'Error on line ' + str(sys.exc_info()[-1].tb_lineno) + ' - ' + str(type(e).__name__) + ' - ' + str(e) + '. Context: ' + context print error feature.setAttribute("_error", error)


chriswilson
Supporter
Forum|alt.badge.img+11
  • Supporter
  • March 8, 2018
This really would help, too often the python caller errors are difficult to debug and having the features outputted through this port would be great.

 

 


tcrossman
Contributor
Forum|alt.badge.img+1
  • Contributor
  • March 21, 2022

Why not go whole hog and have a Python-based transformer designer API?


LizAtSafe
Safer
Forum|alt.badge.img+15
  • Safer
  • February 10, 2024
The following idea has been merged into this idea:
https://community.safe.com/ideas/add-rejected-port-to-the-pythoncaller-31481
All the votes have been transferred into this idea.

LizAtSafe
Safer
Forum|alt.badge.img+15
  • Safer
  • February 10, 2024
NewIn Development

LizAtSafe
Safer
Forum|alt.badge.img+15
  • Safer
  • February 10, 2024
Updated idea statusIn DevelopmentIn Technical Review

danilo_fme
Evangelist
Forum|alt.badge.img+42
  • Evangelist
  • February 24, 2024
david_r wrote:

It would be very helpful to have a Rejected-port on the PythonCaller.

Outputting a failed feature could e.g. look like this:

self.pyoutput_failed(feature)

This would simplify the pattern where you need to set a temporary attribute to indicate success/failure, then a Tester and then removing the temporary attribute.

It can be very interesting for all users.


vlroyrenn
Supporter
Forum|alt.badge.img+12
  • Supporter
  • March 12, 2024

Seems like it has been added to FME 2024.0, according to fmetools.


vlroyrenn
Supporter
Forum|alt.badge.img+12
  • Supporter
  • March 14, 2024

I’m noticing a few things with the 2024.0 changes to support this in the underlying PythonFactory:

  • The PythonCaller itself hasn’t yet been modified to allow multiple ports
  • The default template of the Transformer Designer still uses an upstream TeeFactory and a downstream TestFactory to simulate multiple output ports instead of plugging all ports in the PythonFactory directly
  • Calling self.pyoutput(feature, "<REJECTED>") will end the workflow as soon as the Rejected feature appears, like most other transformers, but a raised exception (not even an FMEException) will not be turned into a rejected feature. Errors still need to be properly caught and the feature created by the exception handler.
  • The close() method only fires once and does not have a per-port identifier, meaning a Python script cannot validate that individual input ports have finished sending data. This means transformers that need to wait for all features to have come through to start outputting can’t do so independently between ports, and order-critical operations can’t be asserted.

vlroyrenn
Supporter
Forum|alt.badge.img+12
  • Supporter
  • March 14, 2024

 I made a 2024.0-only PythonCaller clone with a <Rejected> port just to try it out and show how it works. I can’t attach any files here, and this is by no means tested enough to be uploaded to FME Hub, so I’m just going to dump the contents of the FMXJ file here.

Documents\FME\Transformers\PythonCallerRejectable.fmxj

{
    "info": {
        "categories": [
            "Workflows"
        ],
        "comments": [
            ""
        ],
        "name": "PythonCallerRejectable"
    },
    "versions": [
        {
            "changeLog": [
                "Initial version of transformer"
            ],
            "comments": [
                "================================ Version 1 ========================================"
            ],
            "executionTemplate": [
                "",
                "FACTORY_DEF {*} PythonFactory",
                "   FACTORY_NAME { $(XFORMER_NAME) }",
                "   PY_INPUT_TAGS Input",
                "   $(INPUT_LINES)",
                "   SYMBOL_NAME { $(PYTHONSYMBOL) }",
                "   PYTHON_NAMESPACE FMEOBJECTS",
                "   SOURCE_CODE { $(PYTHONSOURCE) }",
                "    PY_OUTPUT_TAGS Output <REJECTED>",
                "    OUTPUT { Output FEATURE_TYPE $(OUTPUT_Output_FTYPE)",
                "        $(OUTPUT_Output_FUNCS) }",
                "    OUTPUT { <REJECTED> FEATURE_TYPE $(OUTPUT_<REJECTED>_FTYPE)",
                "        $(OUTPUT_<REJECTED>_FUNCS) }",
                ""
            ],
            "featureHolding": "none",
            "form": {
                "parameters": [
                    {
                        "name": "TRANSFORMER_GROUP",
                        "parameters": [
                            {
                                "name": "XFORMER_NAME",
                                "prompt": "Transformer Name",
                                "showEditButton": false,
                                "supportedValueTypes": [
                                    "none"
                                ],
                                "type": "text",
                                "valueType": "string"
                            }
                        ],
                        "prompt": "",
                        "type": "group",
                        "valueType": "string"
                    },
                    {
                        "defaultValue": "",
                        "name": "PARAMETERS_GROUP",
                        "parameters": [
                            {
                                "defaultValue": "FeatureProcessor",
                                "editor": "plaintext",
                                "name": "PYTHONSYMBOL",
                                "prompt": "Class to Process Features",
                                "required": true,
                                "showArithmeticEditor": false,
                                "showEditButton": false,
                                "showInlineEditor": false,
                                "showPrompt": true,
                                "supportedValueTypes": [
                                    "none"
                                ],
                                "trimWhitespace": true,
                                "type": "text",
                                "valueType": "string"
                            },
                            {
                                "defaultValue": "import<space>fme<lf>import<space>fmeobjects<lf><lf>class<space>FeatureProcessor<openparen>object<closeparen>:<lf><space><space><space><space><quote><quote><quote>Template<space>Class<space>Interface:<lf><space><space><space><space>When<space>using<space>this<space>class<comma><space>make<space>sure<space>its<space>name<space>is<space>set<space>as<space>the<space>value<space>of<space>the<space><apos>Class<space>to<space>Process<space>Features<apos><lf><space><space><space><space>transformer<space>parameter.<lf><space><space><space><space><quote><quote><quote><lf><lf><space><space><space><space>def<space>__init__<openparen>self<closeparen>:<lf><space><space><space><space><space><space><space><space><quote><quote><quote>Base<space>constructor<space>for<space>class<space>members.<quote><quote><quote><lf><space><space><space><space><space><space><space><space>pass<lf><space><space><space><space><space><space><space><space><lf><space><space><space><space>def<space>has_support_for<openparen>self<comma><space>support_type:<space>int<closeparen>:<lf><space><space><space><space><space><space><space><space><quote><quote><quote>This<space>method<space>is<space>called<space>by<space>FME<space>to<space>determine<space>if<space>the<space>PythonCaller<space>supports<space>Bulk<space>mode<comma><lf><space><space><space><space><space><space><space><space>which<space>allows<space>for<space>significant<space>performance<space>gains<space>when<space>processing<space>large<space>numbers<space>of<space>features.<lf><space><space><space><space><space><space><space><space>Bulk<space>mode<space>cannot<space>always<space>be<space>supported.<space><lf><space><space><space><space><space><space><space><space>More<space>information<space>available<space>in<space>transformer<space>help.<lf><space><space><space><space><space><space><space><space><quote><quote><quote><lf><space><space><space><space><space><space><space><space>return<space>support_type<space>==<space>fmeobjects.FME_SUPPORT_FEATURE_TABLE_SHIM<lf><space><space><lf><space><space><space><space>def<space>input_from<openparen>self<comma><space>feature:<space>fmeobjects.FMEFeature<comma><space>input_tag:<space>str<closeparen>:<lf><space><space><space><space><space><space><space><space><quote><quote><quote>This<space>method<space>is<space>called<space>for<space>each<space>feature<space>which<space>enters<space>the<space>PythonCaller.<space><lf><space><space><space><space><space><space><space><space>Processed<space>input<space>features<space>can<space>be<space>emitted<space>from<space>this<space>method<space>using<space>self.pyoutput<openparen><closeparen>.<lf><space><space><space><space><space><space><space><space>If<space>knowledge<space>of<space>all<space>input<space>features<space>is<space>required<space>for<space>processing<comma><space>then<space>input<space>features<space>should<space>be<space><lf><space><space><space><space><space><space><space><space>cached<space>to<space>a<space>list<space>instance<space>variable<space>and<space>processed<space>using<space>group<space>processing<space>or<space>in<space>the<space>close<openparen><closeparen><space>method.<lf><space><space><space><space><space><space><space><space><quote><quote><quote><lf><space><space><space><space><space><space><space><space>try:<lf><space><space><space><space><space><space><space><space><space><space><space><space>#<space>foo<space>=<space>1<solidus>0<lf><space><space><space><space><space><space><space><space><space><space><space><space>self.pyoutput<openparen>feature<comma><space><quote>Output<quote><closeparen><lf><space><space><space><space><space><space><space><space>except<space>Exception<space>as<space>exc:<lf><space><space><space><space><space><space><space><space><space><space><space><space>feature.setAttribute<openparen><quote>fme_rejection_code<quote><comma><space><quote>error<quote><closeparen><lf><space><space><space><space><space><space><space><space><space><space><space><space>feature.setAttribute<openparen><quote>fme_rejection_message<quote><comma><space>str<openparen>exc<closeparen><closeparen><lf><space><space><space><space><space><space><space><space><space><space><space><space>self.pyoutput<openparen>feature<comma><space><quote><lt>REJECTED<gt><quote><closeparen><lf><space><space><space><space><space><space><space><space><space><space><space><space><lf><lf><space><space><space><space>def<space>close<openparen>self<closeparen>:<lf><space><space><space><space><space><space><space><space><quote><quote><quote>This<space>method<space>is<space>called<space>once<space>all<space>the<space>FME<space>Features<space>have<space>been<space>processed<space>from<space>input<openparen><closeparen>.<quote><quote><quote><lf><space><space><space><space><space><space><space><space>pass<lf>",
                                "editor": "python",
                                "name": "PYTHONSOURCE",
                                "prompt": "Enter Text Data",
                                "required": true,
                                "showEditButton": false,
                                "showInlineEditor": true,
                                "showPrompt": false,
                                "supportedValueTypes": [
                                    "none"
                                ],
                                "type": "text",
                                "valueType": "stringEncoded"
                            }
                        ],
                        "prompt": "Parameters",
                        "required": false,
                        "type": "group",
                        "valueType": "string"
                    }
                ]
            },
            "inputPorts": [
                {
                    "name": "Input"
                }
            ],
            "outputPorts": [
                {
                    "name": "Output"
                },
                {
                    "attributesAdded": [
                        {
                            "name": "fme_rejection_code",
                            "type": "buffer"
                        },
                        {
                            "name": "fme_rejection_message",
                            "type": "buffer"
                        }
                    ],
                    "name": "<REJECTED>"
                }
            ],
            "upgradeWarning": "",
            "version": 1
        }
    ]
}


 


vlroyrenn
Supporter
Forum|alt.badge.img+12
  • Supporter
  • March 15, 2024
vlroyrenn wrote:
  • The close() method only fires once and does not have a per-port identifier, meaning a Python script cannot validate that individual input ports have finished sending data. This means transformers that need to wait for all features to have come through to start outputting can’t do so independently between ports, and order-critical operations can’t be asserted.

 

 

I made that last point into its own idea, since it would be an expansion on what has already been added to fix the current one.

 


andreaatsafe
Safer
Forum|alt.badge.img+10
  • Safer
  • September 23, 2024

I’m pleased to let you know that we have added a rejected port to the PythonCaller via the reject_feature() method.

This is available in the upcoming release of 2024.2, you can try it out now in the beta: https://fme.safe.com/downloads/#beta


LizAtSafe
Safer
Forum|alt.badge.img+15
  • Safer
  • September 23, 2024
Gathering InterestReleased

Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings