#Native code functions

A technical description will be given now on the internal structure of the functional nodes. In particular, the guide will go through the following main items:

Function print()

Quite analogous to the native Python print function, the print command allows printing into the simulator for debugging purposes.

The output of the print function will be displayed in the Logs / Application window of the simulator.

Example of print and log

Functions exit() and assign()

The returns defined in the Returns configuration table can now be used in the source code to define the output of the function.

The CPA user can write an arbitrary list of instruction flows and assign them to a particular output.

     void: assign(    
            result, <-- (mandatory) integer that identifies the return Id 
            value,  <-- (optional) string describing the result
            key     <-- (optional) key describing the result    
      )

      void: exit(
            result,  <-- (mandatory) integer that identifies the return Id
            value,  <-- (optional) string describing the result
            key  <-- (optional) key describing the result
       )

When value and key parameters are not assigned, the system will retrieve the value and key parameters associated with the specified id in the Returns table.

The exit() function terminates the execution of the function, while assign() does not.

For example:

print("Print message number one")  
assign(1)  
print("Print message number two")
exit(1)  
print("This and the following code lines will not be executed")

Let’s consider the function is_email(), which has two returns,

  1. Valid email address
  2. Not valid email address

and will try to parse a textual variable as an email address: if it fails, exit(2) will be called, otherwise exit(1).

A possible code of the is_email() function can be the following:

import re       # re is the Regular Expression Python library

try:

# The textual variable to validate
var = "my_email@email.com"
# The email regular expression
regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'

# Match regular expression with the variable
if re.fullmatch(regex, var):
    exit(1)  # Valid email address
else:  
    exit(2)  # Not valid email address

except Exception as e:
    print("Got the following unexpected error in the execution: " + str(e))
    exit(2)  # Not valid email address

⚠️Important note: Every function must end with an exit() or an assign() call.
If missing, the simulator will raise the following error:

[x] No reference to 'assign()' or 'exit()' procedures found. Be sure that the source code always ends with an assignment 

⚠️Important note: A best practice would be to create at least two or more returns, where one is to catch any possible error, and encapsulate the entire source code into a try ... except statement as described below:

try:

    ...  ← write here the body of the function
    exit(1)
except Exception as e:
    print("Got the following unexpected error in the execution: " + str(e))
    exit(2)

Function get()

Variables assigned to nodes can be retrieved into the source code with the function get.

Function get is defined as below:

dict: get(
        var_name,
        index = 0
)

where var_name is the name of the variable to retrieve.

A variable can be overridden multiple times: by default, the get function will return the last occurrence of the variable, unless a value of the index greater than 0 is specified.

The result of the get function is a dictionary described as follows:

{
    "key": "key of var one",
    "value": "value of var one",
    "tms": "2022-01-01 10:00:00.0000"
} 

where value and key are the information of the selected variable and tms is the timestamp with format YYYY-MM-DD HH24:MI:SS.FFFF of the most recent assignment.

A list of example commands and answers is reported below:

1.  print(get("var1"))
2.  > {"value":"Text associated to variable #1", "key":"", "tms":"2022-01-01 10:00:00.0000"}
3.  print(get("var1")["value"])
4.  > "Text associated to variable #1"
5.  print(get("var1")["key"])
6.  > ""
7.  print(get("var2"))
8.  > {"value":"12345", "key":"999", "tms":"2022-01-01 10:00:00.0000"}
9.  print(get("var2")[0])
10. > {"value":"12345", "key":"999", "tms":"2022-01-01 10:00:00.0000"} 
11. print(get("var2")[2])
12. > {"value":"", "key":"", "tms":""}
13. print(get("var2")["key"])
14. > "999"
15. print(get("not_exsisting_var"))
16. > {"value":"", "key":"", "tms":""}

Function getMedia()

The function getMedia() allows the function to retrieve the list of the media contents uploaded by a user.

list: getMedia(get_raw = False)

The result is the following list:

[
    {
        "hash": "a36ccb...ae3f6ea",
        "filename": "MyImageOne.jpeg",
        "url": "https://ue.prod.app.spixii.ai/tus_php/app/fetch/?bot_id=12...53&k=a36ccb...ae3f6ea",
        "tms": "2022-01-01 00:00:00.000000"
    },
    {
        "hash": "37126f...83fc2e",
        "filename": "MyVideoOne.mov",
        "url": "https://ue.prod.app.spixii.ai/tus_php/app/fetch/?bot_id=12...53&k=37126f...83fc2e",
        "tms": "2022-01-01 10:00:00.000000"
    },
    ...
] 

where

The getMedia() will return an empty list if no media content was uploaded. If the flag get_raw is True then the getMedia(true) will return the same list as before including the base64 encoding of each media content.

[
        {
            "hash": "37126f...83fc2e",
            "filename": "MyVideoOne.mov",
            "url": "https://ue.prod.app.spixii.ai/tus_php/app/fetch/?bot_id=12...53&k=37126f...83fc2e",
            "tms": "2022-01-01 10:00:00.000000",
            "raw": "SlBFR1...B0ZXN0b2hsb2dpYQ=="
        },
        ...
]

⚠️Important note: Media contents will not be available from the getMedia() if the end-user selected from the chat the option to delete them.

⚠️Important note: The URLs of media contents can be easily sent to an external service or API. Let's consider for example a home claim process which asks the user to provide with some images and videos of the building loss. Then, the media contents may be passed to the claim handler system with a fragment of code similar to the following:

# Get claimant variables
name = get('name')['value']
surname = get('surname')['value']
address = get('address')['value']

# Get media contents
mediaContents = getMedia()
if not mediaContents:
  evidences = []
else:
  evidences = [el.url for el in mediaContents]

# Create a claim
claim = {
  "name": name,
  "surname": surname,
  "address": address,
  "evidences": evidences
}

# Send to the claim handler system
req = requests.post("<URL of the claim handler system", json.dump(claim))
res = req.json()

print("Claim registered with claim Id: " + str(res['claim_id']))

If the claim handler system requires the media contents themselves instead of the URLs, then the following fragment can be used to download, encode and dispatch the media contents straight. Note that raw media contents may take long to be downloaded and encoded.

# Loop over each media content, including the raw data
mediaContents = getMedia(True)
if not mediaContents:
  evidences = []
else:
  evidences = [el.raw for el in mediaContents]

print("Below the encoded media contents (may be a long sequence of characters!)")
print(evidences)

Function save()

Result of elaborations, new variables or API responses can be saved and used by other pieces of the conversation.

The save() function allows storing variables that can be retrieved with the get() function into the source code or with the {{}} notation in the text of the conversation.

bool: save(
    var_name,  ← (mandatory) name of the variable
    value = "",  ← (optional) value to store
    Key = ""  ← (optional) key to store
)

The function returns a Boolean true if the save process was successful, false otherwise.

Calling the save() function over an already existing variable will overwrite its value and key.

Some examples are reported below:

1.  save("myvar1","value at time 1", "key at time 1")
2.  save("myvar1", "value at time 2", "key at time 2")
3.  save("myvar1", "value at time 3")
4.  print(get("myvar1"))
5.  print(get("myvar1")[1])
6.  print(get("myvar1")[2])
7.  print(get("myvar1")[100])

The console log will contain the following information:

1.  {"value":"value at time 3", "key":"", "tms":"2022-01-01 10:00:03.0000"}
2.  {"value":"value at time 2", "key":"key at time 2", "tms":"2022-01-01 10:00:02.0000"}
3.  {"value":"value at time 1", "key":"key at time 1", "tms":"2022-01-01 10:00:01.0000"}
4.  {"value":"", "key":"", "tms":""}

Function bind()

The function bind() is a compact tool to embed CPA variables into a template, which is represented by a string. It becomes particularly useful when it’s necessary to create an API payload starting from a template with many placeholders.

Below it’s reported the definition:

str: bind(
    template = "",  ← (mandatory) string containing the placeholders
    arr = {}  ← (optional) dictionary of variables to bind
)

Here is an example of the result of the application of the bind() function. Let’s assume the following assignment of variables:

the application of the bind() function will be:

template = "Hi {{name}}! You chose the option {{opt.key}}. Bind {{no_variable}}"
result = bind(template)  
print(result)  
# "Hi Giskard! You chose the option Opt 1. Bind "  

result = bind(template, {"name":"Gaia","no_variable":"this"})
print(result)  
# "Hi Gaia! You chose the option Opt 1. Bind this"

Usage of the library importlib

Python uses a number of mechanisms to import libraries and modules: import, importlib.import_module() and __import__() are just a few examples.

Those methods are enabled in the CPA, but, to keep a high-security level of the conversational process, not all the packages can be imported. Also, most of the Python built-in objects are available and just some of them are adjusted or filtered out.

Let’s take for example the module os, which is partially forbidden as it may allow a malicious user to execute a potentially dangerous code into the server.

The source code

import os  
os.system("ls -l")

will return the following message in the Log Error console:

[x] Security error in importing a Python module: package "os" is forbidden. Will be ignored

The list of all the importable modules can be extracted with the following command:

for el in dir():
    print(el)

Among this list, there are three Extension modules: Requests, ReadFile and SQLAlchemy. They can be imported if they were included in the licence subscription.

Spixii keeps this list updated and validates periodically new packages and modules that can be included and used by the CPA users in their custom function implementations.