Python and relative imports in AWS Lambda Functions

nr18

Joris Conijn

Posted on December 14, 2022

Python and relative imports in AWS Lambda Functions

When writing an AWS Lambda function, it’s quite possible that you get to the point where the file becomes too big. So, what do you do? You create a second file and refactor your code to do a relative import. Then, the only step left is to deploy your code and run it. And then it fails… Have you been here before? Then I might have some sound advice for you.
If you look at the PEP 8 — Style Guide for Python Code.

You will read that absolute imports are recommended. But, explicit relative imports are an acceptable alternative to absolute imports.

This means that you should favor absolute imports. And only use relative imports within a package.

Sample code

Let’s create a hello world lambda function to show this. So we have the following code in a file called hello_world/app.py:

import json

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({"message": "hello world"}),
    }
Enter fullscreen mode Exit fullscreen mode

For simplicity reasons I used the AWS Serverless Application Model.

The templates contain the following resource:

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
Enter fullscreen mode Exit fullscreen mode

The most important thing is the Handler definition. The value app.lambda_handler translates to:

Use the app.py file and invoke the lambda_handler method.

We will now move the payload to a file called hello_world/response.py:

def get_response() -> dict:
    return {"message": "hello world"}
Enter fullscreen mode Exit fullscreen mode

And update the hello_world/app.py:

import json
from .response import get_response

def lambda_handler(event, context):

    return {
        "statusCode": 200,
        "body": json.dumps(get_response()),
    }
Enter fullscreen mode Exit fullscreen mode

You run your test and it works, nice! So we will now deploy our code and that it also works. And it fails…

[ERROR] Runtime.ImportModuleError: Unable to import module 'app': attempted relative import with no known parent package
Traceback (most recent call last):
Enter fullscreen mode Exit fullscreen mode

The content of the hello_world folder ends up in the /var/task/ folder. And not the folder itself.

So it might look like that the code is in a package but from the Lambda runtime it’s not.

The solution

You can solve this by creating a package. How you might ask? Create a hello_world folder in the hello_world folder.

Move all files except the requirements.txt into the created folder.

Example of the folder structure

Next you need to update the Handler in the template:

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: hello_world.app.lambda_handler
      Runtime: python3.9
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
Enter fullscreen mode Exit fullscreen mode

Now the value of the Handler translates to:

In the hello_world package you use the app.py file and invoke the lambda_handler method.

When you deploy you will now notice that the relative import works.

Conclusion

I would always tell you to deploy your python code in a package. It makes it easier to use many files and organize your code better.

It also allows you to separate the Lambda invocation logic from your business logic. Making it easier to test and maintain.

Image by Gerd Altmann from Pixabay

💖 💪 🙅 🚩
nr18
Joris Conijn

Posted on December 14, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related