Salesforce Functions: Getting Started with Python

Salesforce Functions: Getting Started with Python
Photo by NASA / Unsplash

I'm super excited about the introduction of Salesforce Functions and their seemingly limitless possibilities and use cases. Though it's a feature that has been generally available since October 2021, I just started getting my hands dirty and created/ran the boilerplate Python function.

"Salesforce Functions lets you extend Salesforce business logic to meet any demand. With Functions, you write code in industry-standard programming languages, and run your code within the Salesforce trust boundary. When your code runs, it automatically scales with load and doesn’t count against your Salesforce request limits."

Functions Developer Guide Overview

"Salesforce Functions lets you use industry-standard programming languages and frameworks. Your team now can use their preferred language and tools to develop Salesforce solutions, instead of having to write or rewrite code in a Salesforce-specific language. Your Salesforce app can now use open-source or third-party frameworks, saving you development time and effort."

This is big, and currently offers language support for Javascript, Python, Java, and Typescript. Since I thought the documentation was a little difficult to track down for the Python version, I put together a short description of my process for anyone interested.

First, if you haven't already, switch to the latest version of the Salesforce CLI. This provides easy command line access for generating and running Functions.

Create a new salesforce project, and navigate to the directory.

$ sf project generate --name fx-playground
$ cd fx-playground

Make sure to set a default Org for your project. I created a scratch org for my project and called it FXTest, but an existing connection you have to a sandbox will work as well.

$ sf org create scratch --target-dev-hub <YourHub> --definition-file config/project-scratch-def.json --set-default --alias FXTest

Next, create your Python function. This adds a functions/ directory to your Salesforce project and builds the basic framework for you.

$ sf generate function --function-name myfunction --language python

The file structure will look like this:

fx-playground
├── functions
|   ├── myfunction
|   │   ├── tests
|   │   |   ├── __init__.py
|   │   |   ├── test_main.py
|   │   ├── main.py
|   │   ├── payload.json
|   │   ├── project.toml
|   │   ├── README.md
|   │   ├── requirements-dev.txt
|   │   ├── requirements.txt

In main.py we've got a basic function that pulls back all the Accounts in your Org.

from typing import Any

from salesforce_functions import Context, InvocationEvent, get_logger

# The type of the data payload sent with the invocation event.
# Change this to a more specific type matching the expected payload for
# improved IDE auto-completion and linting coverage. For example:
# `EventPayloadType = dict[str, Any]`
EventPayloadType = Any

logger = get_logger()


async def function(event: InvocationEvent[EventPayloadType], context: Context):
    """Describe the function here."""

    result = await context.org.data_api.query("SELECT Id, Name FROM Account")
    logger.info(f"Function successfully queried {result.total_size} account records!")

    return result.records
main.py

Now were ready to complete the configuration of our Function in a similar manner to setting up a basic Python project.

Navigate to your Function's directory:

$ cd functions/myfunction

The salesforce-functions library requires that you use Python 3.10, so if you haven't already, install it and activate using your environment manager of choice. I'm using conda with a 3.10 base:

$ conda activate base
$ python -V
---
Python 3.10.12
(base)

Next, install the requirements in your virtual environment.

$ pip install -r requirements-dev.txt

Run the Function!

$ sf run function start --language python
---
WARNING: Python support for Salesforce Functions is experimental.
Starting sf-functions-python v0.6.0 in single process mode.
INFO:     Started server process [29657]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://localhost:8080 (Press CTRL+C to quit)

Your Function is now running locally on localhost:8080 and ready to catch a payload. Since we didn't change anything in the boilerplate code the Function isn't expecting anything specific in the payload. Open up another terminal window and run:

$ sf run function --function-url http://localhost:8080 --payload '{}'
---
Using target-org FXTest login credential to initialize context
POST http://localhost:8080... 200
[
  {
    "type": "Account",
    "fields": {
      "Id": "0018F00000QgbtHQAR",
      "Name": "Sample Account for Entitlements"
    },
    "sub_query_results": {}
  }
]

And there it is! The one account from my new scratch org. The sky is the limit from here, I'm just starting to think about all the possibilities of a microservice-like infrastructure housed within Salesforce.