Skip to main content
Released

Add Rejected-port to the PythonCaller

Related products:Transformers
dporchetti
toby_c
+132
  • dporchetti
    dporchetti
  • benkelvin
  • aidanjohns
  • mjfuchs
  • toby_c
    toby_c
  • mnikosz
  • mkludas
    mkludas
  • vlroyrenn
    vlroyrenn
  • holmescm01
    holmescm01
  • jbeamish
    jbeamish
  • debbiatsafe
    debbiatsafe
  • siennaatsafe
    siennaatsafe
  • david_r
    david_r
  • nielsgerrits
    nielsgerrits
  • ebygomm
    ebygomm
  • takashi
    takashi
  • danilo_fme
    danilo_fme
  • jdh
    jdh
  • lifalin2016
    lifalin2016
  • sigtill
    sigtill
  • warrendev
    warrendev
  • tcrossman
    tcrossman
  • vxn43
    vxn43
  • jkr_wrk
    jkr_wrk
  • j.botterill
    j.botterill
  • carmijo
    carmijo
  • oscard
    oscard
  • tomf
    tomf
  • birgit
    birgit
  • bruceharold
    bruceharold
  • fgiron
    fgiron
  • larue
    larue
  • mathiku
    mathiku
  • lorenrouth
    lorenrouth
  • nic_ran
    nic_ran
  • jelle
    jelle
  • geosander
    geosander
  • panda
    panda
  • kennyo
    kennyo
  • chriswilson
    chriswilson
  • mark_f
  • canerakin
    canerakin
  • fhilding
  • arthur_bazin
    arthur_bazin
  • matthieuv
    matthieuv
  • bibold
  • vki
    vki
  • revesz
    revesz
  • jasperwis
    jasperwis
  • richsnyder0
  • jpsalva
    jpsalva
  • gerhard
  • simonbea
  • apence231
    apence231
  • gpoehacker
  • peterx
  • nicohauri
    nicohauri
  • ev_robin
    ev_robin
  • ml56067
    ml56067
  • raean
    raean
  • jnotter
    jnotter
  • dataninja
    dataninja
  • jarrydhunter
    jarrydhunter
  • davekazemi
    davekazemi
  • cwarren
    cwarren
  • paulbrandt73
  • simonl
    simonl
  • simon11
  • aaron_chaput
    aaron_chaput
  • jiranek_hsro
    jiranek_hsro
  • abal
    abal
  • mvanwie
    mvanwie
  • abizien
    abizien
  • annabayona
    annabayona
  • yongjie
    yongjie
  • blll_daigle
    blll_daigle
  • patrick_koning
    patrick_koning
  • elias
  • ecthelion
    ecthelion
  • kd
  • maayke
    maayke
  • tslack
  • felipeverdu
    felipeverdu
  • johnt
    johnt
  • marnickcle
    marnickcle
  • roland.martin
    roland.martin
  • becchr
    becchr
  • cfvonner
    cfvonner
  • stipica.pavicic
    stipica.pavicic
  • krisvesweco
    krisvesweco
  • tara_he
    tara_he
  • jeroen
    jeroen
  • adrian_farrell
    adrian_farrell
  • anari
    anari
  • tris_w
    tris_w
  • bobw
    bobw
  • taojunabc
    taojunabc
  • verdoodtdries
    verdoodtdries
  • jstanger
    jstanger
  • frro
    frro
  • ediaze
    ediaze
  • jseigneuret
    jseigneuret
  • bmdull
  • adriano
    adriano
  • geoal
    geoal
  • spatialcase
    spatialcase
  • jonas.persson
  • jpvo
    jpvo
  • wicki
    wicki
  • johnmarcdechaud
    johnmarcdechaud
  • stevenjh
    stevenjh
  • grahamwood_hwc
    grahamwood_hwc
  • deanrother
  • jmiddel
    jmiddel
  • jorge_vidinha
    jorge_vidinha
  • katrinaopperman
  • jsdbroughton
    jsdbroughton
  • phoeffler
    phoeffler
  • denniswilhelm
    denniswilhelm
  • soeren
    soeren
  • mchauvet
    mchauvet
  • spatial_k
  • nojo
    nojo
  • pelbe3
  • battlezone77
  • allfrau
  • pudleinere
    pudleinere
  • conterraclara
  • firework_girl
  • camw
  • rhansen
  • bleuminkgeo
  • samuelvaldez
  • mstecker
  • laurawatsafe
    laurawatsafe
  • liamfez
    liamfez
  • laurenc
    laurenc

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
Influencer
  • 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+43
  • 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