Python and relative imports in AWS Lambda Functions
Tags: relative, imports, aws, lambda
Published: 2022-12-14 10:35:29

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](https://www.python.org/dev/peps/pep-0008/#imports).  
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`:

```python
import json

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

For simplicity reasons I used the [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html).  
The templates contain the following resource:

```yaml
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
```

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`:

```python
def get_response() -> dict:
    return {"message": "hello world"}
```

And update the `hello_world/app.py`:

```python
import json
from .response import get_response

def lambda_handler(event, context):

    return {
        "statusCode": 200,
        "body": json.dumps(get_response()),
    }
```

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

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

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o1vjvgpixllb666ade2d.png "Your folder structure should now look like this")

Next you need to update the `Handler` in the template:

```yaml
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
```

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](https://pixabay.com/users/geralt-9301/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=6585578) from [Pixabay](https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=6585578)