Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 11 Current »

The Python post-processing step allows you to run custom python scripts for your Forms.

By combining it with a bash setup script you can prepare your environment (install dependencies, create folder structures, …). The script runs in an isolated space inside its own docker container.

Please keep in mind that Python support is an preview stage. So expect to encounter issues! If so, please report them to us, thanks!

The functionality of your own scripts cannot be guaranteed in future versions of Portrait.

Environment

The Python Runtime we provide is a customized version of the official Python image. Specifically, we use python:3-alpine as the base image. This means that the specific Python version is determined for each release of Portrait.

In addition, we added the following packages:

  • SSHD server for running the scripts remotely. (Access meant only internally for Portrait Docker services)

    • There are two SSH keys for the communication pythonRuntime_id_rsa.pem (stored in the jar inside the backend container) and pythonRuntime_id_rsa.pub (stored inside the scripting container)

  • Pyenv

  • Pipenv

  • Venv

  • common packages: vim, nano, git, curl, bash

  • some build dependencies are needed for pyenv, pipenv, venv

The container is connected to the same network as the other portrait services. So you are able to access the Portrait API as well as other APIs or web services.

Configuration

onSubmit with Python supports the following settings:

onSubmit.type

Must be Python

onSubmit.scriptName

name of the python file.

onSubmit.setupScriptName

name of a bash setup script that is called on each submit before the Python script. Can be used to install dependencies via pip .

There is a runtime limit of 15 seconds for the combination of setup script and python script.
This can be changed by overwriting the config property microservices.pythonRuntime.timeoutMs in the YML

Setup Scripts

The setup script needs to be a shell script, which is called each time the form is submitted. The main purpose is to install additional dependencies through pip. The Portrait server will call this script without any parameters.

While the script can be used for any purpose, we recommend keeping it to a minimum and moving your business logic into the actual python script.

Keep in mind that the execution will take place ON EACH form submit!
You have to set up your own logic to check whether packages are already installed.

If you stick to 'pip install <package>' commands, Pip will skip this command if a package is already installed. Therefore, the process will finish faster on consecutive runs.

The setup script and the main process runs in a different session. So changes that are not permanent (local env variables, pipenv shell, …) are not visible in the main script.

Hence, virtual environments (like pipenv or pyenv) can’t be used currently. Therefore, all scripts share the same python installation.

This might change in the future.

Python Scripts

The python script will contain the actual business logic and will be executed after the setup script execution finished.

Preparation

The script will be called via the command line. You can expect the following inputs:

  • --scriptExecutionID 0620f736-68a1-45e7-b411-f71cb7d6ab8c

    • a UUID identifying this specific execution

  • --files /app/fileparameter/0620f736-68a1-45e7-b411-f71cb7d6ab8c/94141861p1-1670253160678.png

    • List of files that were submitted with the form

    • These files are available under the provided path and will be automatically removed after the script is finished. The files will also be removed if the scripts fails/crashes.

  • --args

    • The form submit information formatted as JSON

    • In addition, this JSON will also contain the portrait user information

    • Example

    • {
          "responses": [
              {
                  "values": [
                      "IssueText"
                  ],
                  "name": "Name"
              },
              {
                  "values": [
                      "CREATE"
                  ],
                  "name": "portrait-action"
              },
              {
                  "values": [
                      "2023-05-16T11:34:23.478Z"
                  ],
                  "name": "portrait-timestamp"
              },
              {
                  "values": [
                      "xiaoNgRRPsqdclKyAvbZkaYmfGiosEYaSoiFSMkNGXFrSSOJAwUH"
                  ],
                  "name": "PORTRAIT_USER_ID"
              },
              {
                  "values": [
                      "Linda Jackson"
                  ],
                  "name": "PORTRAIT_USER_NAME"
              },
              {
                  "values": [
                      "linda.jackson@larsens.portraitapp.co"
                  ],
                  "name": "PORTRAIT_USER_EMAIL"
              },
              {
                  "values": [
                      "ADMIN"
                  ],
                  "name": "PORTRAIT_USER_ROLE"
              }
          ]
      }

Example call

python /app/scripts/demo.py --scriptExecutionID 0620f736-68a1-45e7-b411-f71cb7d6ab8c --files /app/fileparameter/0620f736-68a1-45e7-b411-f71cb7d6ab8c/94141861p1-1670253160678.png /app/fileparameter/0620f736-68a1-45e7-b411-f71cb7d6ab8c/101454734p0-1670253163775.jpg --args '{"responses":[{"values":["Akku Tausch"],"name":"Name"},{"values":["iPhone XR"],"name":"ModelCode"},{"values":["3 - High"],"name":"Priority"},{"values":["94141861p1-1670253160678.png","101454734p0-1670253163775.jpg"],"name":"Photos"},{"values":["CREATE"],"name":"portrait-action"},{"values":["2022-12-05T15:12:44.581Z"],"name":"portrait-timestamp"}]}'

Your custom script

In the script, you could create a PDF, call a web service, or implement business logic to your needs.

You have access to the input files provided via the command line parameters.
In addition, you can import any python libs that are either manually installed on the system or installed in the setup script.

Output

  • If the script returns an Exit code of 0, Portrait will treat it as finished successfully.

  • If the script writes anything to standard error or a non-zero exit status was received, then Portrait will treat it as finished unsuccessfully.

Cleanup

After the script is called, the /app/fileparameter/0620f736-68a1-45e7-b411-f71cb7d6ab8c folder is deleted.

Simple Example

    - id: createServiceCallPython
      onSubmit:
        type: Python
        setupScriptName: installDependencies.sh
        scriptName: demo.py
      dialog: |-
        { ... } 

Content of the setup script installDependencies.sh:

pip install reportlab

Content of the script demo.py:

import sys

# total arguments
n = len(sys.argv)
print("Total arguments passed:", n)

# Arguments passed
print("\nName of Python script:", sys.argv[0])

print("\nArguments passed:", end = " ")
for i in range(1, n):
    print(sys.argv[i], end = " ")

Output

Total arguments passed: 7
Name of Python script: /app/scripts/demo.py
Arguments passed: Arguments passed: --scriptExecutionID 6f8e6a90-f7ed-4a7c-b224-af84f23b1f10 --files /app/fileparameter/6f8e6a90-f7ed-4a7c-b224-af84f23b1f10/adobestock229193709-1684236862689.png --args {"responses":[{"values":["IssueText"],"name":"Name"},{"values":["ModellText"],"name":"ModelCode"},{"values":["3 - High"],"name":"Priority"},{"values":["adobestock229193709-1684236862689.png"],"name":"Photos"},{"values":["CREATE"],"name":"portrait-action"},{"values":["2023-05-16T11:34:23.478Z"],"name":"portrait-timestamp"},{"values":["xiaoNgRRPsqdclKyAvbZkaYmfGiosEYaSoiFSMkNGXFrSSOJAwUH"],"name":"PORTRAIT_USER_ID"},{"values":["Linda Jackson"],"name":"PORTRAIT_USER_NAME"},{"values":["linda.jackson@larsens.portraitapp.co"],"name":"PORTRAIT_USER_EMAIL"},{"values":["ADMIN"],"name":"PORTRAIT_USER_ROLE"}]}  

Example Use cases

Generate Invoice PDF

    - id: createBill
      onSubmit:
        type: Python
        scriptName: genInvoice.py
        setupScriptName: setupGenInvoice.sh
      dialog: |-
        {
          "title": "Rechnung erstellen",
          "successMessage": "Rechnung erstellt. Verfügbar in config/output in VSCode Web",
          "languageCodeSuccessMessage": "ON_SUBMIT_SUCCESS_MESSAGE",
          "errorMessage": "Fehler beim Hochladen des Formulars. Bitte versuche Sie es später erneut.",
          "languageCodeErrorMessage": "ON_SUBMIT_ERROR_MESSAGE",
          "submitText": "Speichern",
          "languageCodeSubmitText": "SUBMIT_NOW",
          "description": "Mit diesem Formular können Sie ihre Rechnungen erstellen",
          "pages": [
            {
              "label":"Rechnung",
              "fields": [
                {
                  "name": "Name",
                  "displayText": "Empfänger",
                  "description": "Geben Sie den Rechnungsempfänger an",
                  "languageCode": "MOD_TXT_LABEL_SUBJECT",
                  "languageCodeDescription": "MOD_TXT_DESCRIPTION_PLEASE_ENTER_A_SUBJECT",
                  "type": "Textbox",
                  "defaultValue": "",
                  "options": {
                    "required": true,
                    "inputMaskType": "simple"
                  }
                }
              ]
            }
          ]
        }

setupGenInvoice.sh:

pip install reportlab


pythonDependencies Folder contains these files:
- Merchant.ttf
- MerchantWide.ttf


genInvoice.py

# importing modules
import argparse
import json

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas

parser=argparse.ArgumentParser()
parser.add_argument("--args", help="JSON string of arguments")
parser.add_argument("--scriptExecutionID", help="scriptExecutionIDn")
parser.add_argument("--files", help="files")

cmdArgs=parser.parse_args()
scriptExecutionID = cmdArgs.scriptExecutionID

argJson = json.loads(cmdArgs.args)
recipient = argJson["responses"][0]["values"][0]

# initializing variables with values
fileName = '/app/output/bill_' + scriptExecutionID + '.pdf'
documentTitle = 'Rechnung'
title = 'Max Mustermann'

# creating a pdf object
pdf = canvas.Canvas(fileName)

# setting the page size
pdf.setPageSize((222,500))

# setting the title of the document
pdf.setTitle(documentTitle)

# registering a external font in python
pdfmetrics.registerFont(TTFont('Regular', '/app/dependencies/MerchantCopy.ttf'))
pdfmetrics.registerFont(TTFont('Wide', '/app/dependencies/MerchantCopyWide.ttf'))

# creating the title by setting it's font
# and putting it on the canvas
pdf.setFont('Wide', 14)
pdf.drawCentredString(110, 460, 'Max Mustermann')

pdf.setFont("Regular", 14)
pdf.drawCentredString(110, 450, 'Musterstrasse 1')
pdf.drawCentredString(110, 440, 'AT-1020 Wien')
pdf.drawCentredString(110, 430, 'MAIL: max.mustermann@demo.at')
pdf.drawCentredString(110, 420, 'TEL: 0664-12345678')
pdf.drawCentredString(110, 410, '                                      ')
pdf.drawCentredString(110, 400, '                                      ')
pdf.drawCentredString(110, 390, recipient + '                          ')
pdf.drawCentredString(110, 380, 'ABC 12345                             ')
pdf.drawCentredString(110, 370, 'AT-1010 Wien                          ')
pdf.drawCentredString(110, 360, '                                      ')
pdf.drawCentredString(110, 350, '                                      ')
pdf.drawCentredString(110, 340, '           R E C H N U N G            ')
pdf.drawCentredString(110, 330, '                                      ')
pdf.drawCentredString(110, 320, '--------------------------------------')
pdf.drawCentredString(110, 310, 'Re-Nr: 22-004        Datum: 31.11.2022')
pdf.drawCentredString(110, 300, '                                      ')
pdf.drawCentredString(110, 290, 'Anbindung Webshop/FIBU     B    800.00')
pdf.drawCentredString(110, 280, '     8 x   100.00                     ')
pdf.drawCentredString(110, 270, 'Office365 Setup/Interface  B   1700.00')
pdf.drawCentredString(110, 260, '     17 x   100.00                    ')
pdf.drawCentredString(110, 250, '                                      ')
pdf.drawCentredString(110, 240, '--------------------------------------')
pdf.drawCentredString(110, 230, 'Summe                    EUR   2500.00')
pdf.drawCentredString(110, 220, '======================================')
pdf.drawCentredString(110, 210, 'Zahlbar innerhalb 14 Tagen            ')
pdf.drawCentredString(110, 200, '                                      ')
pdf.drawCentredString(110, 190, 'IBAN AT1234123412341234               ')
pdf.drawCentredString(110, 180, 'BIC  ABCDEFGHIJK                      ')
pdf.drawCentredString(110, 170, '                                      ')
pdf.drawCentredString(110, 160, 'Leistungszeitraum:                    ')
pdf.drawCentredString(110, 150, '01.09.2022 - 21.11.2022               ')
pdf.drawCentredString(110, 140, '                                      ')
pdf.drawCentredString(110, 130, 'Umsatzsteuerfrei aufgrund             ')
pdf.drawCentredString(110, 120, 'der Kleinunternehmerregelung          ')
pdf.drawCentredString(110, 110, '                                      ')
pdf.drawCentredString(110, 100, '--------------------------------------')
pdf.drawCentredString(110, 90,  '')
pdf.drawCentredString(110, 80,  'Vielen Dank fuer Ihren Auftrag!')
pdf.drawCentredString(110, 30,  '')
pdf.drawCentredString(110, 20,  '--------------------------------------')

pdf.save()
  • No labels