Search code examples
python-3.xamazon-web-servicesaws-lambdatornadozappa

Error on deploying Python app to AWS Lambda


I have built a Python-Tornado app and am trying to deploy it to AWS Lambda using zappa. But, I am getting an error Error: Warning! Status check on the deployed lambda failed. A GET request to '/' yielded a 502 response code.

My folder structure inside the root folder is :

├── amortization.py
├── config.py
├── dmi-amort-dev-1557138776.zip
├── main.py
├── requirements.txt
├── venv
│   ├── bin
│  
└── zappa_settings.json

zappa deploy dev gives me :

Calling deploy for stage dev..
Downloading and installing dependencies..
- pandas==0.24.2: Using locally cached manylinux wheel
- numpy==1.16.3: Using locally cached manylinux wheel
- sqlite==python3: Using precompiled lambda package
Packaging project as zip.
Uploading dmi-amort-dev-1557143681.zip (30.8MiB)..
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 32.3M/32.3M [00:19<00:00, 1.94MB/s]
Scheduling..
Scheduled dmi-amort-dev-zappa-keep-warm-handler.keep_warm_callback with expression rate(4 minutes)!
Uploading dmi-amort-dev-template-1557143718.json (1.5KiB)..
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1.56K/1.56K [00:00<00:00, 10.6KB/s]
Waiting for stack dmi-amort-dev to create (this can take a bit)..
75%|█████████████████████████████████████████████████████████████████████████████████████████████████████▎                                 | 3/4 [00:09<00:04,  5.00s/res]
Deploying API Gateway..
Error: Warning! Status check on the deployed lambda failed. A GET request to '/' yielded a 502 response code.

zappa tail gives me

Traceback (most recent call last):
File "/var/task/handler.py", line 602, in lambda_handler
return LambdaHandler.lambda_handler(event, context)
File "/var/task/handler.py", line 245, in lambda_handler
handler = cls()
File "/var/task/handler.py", line 142, in __init__
wsgi_app_function = getattr(self.app_module, self.settings.APP_FUNCTION)
AttributeError: module 'main' has no attribute 'app'

zappa_settings.json:

{
    "dev": {
        "app_function": "main.app",
        "aws_region": "ap-south-1",
        "profile_name": "default",
        "project_name": "dmi-amort",
        "runtime": "python3.6",
        "s3_bucket": "zappa-mekp457ye",
        "manage_roles": false,
        "role_name": "lambda-role",
    }
}

main.py:

import tornado.web
from tornado.ioloop import IOLoop
from tornado.web import MissingArgumentError
from config import get_arguments
from amortization import get_amort_schedule

class MainHandler(tornado.web.RequestHandler):
    def prepare(self):
        """Checking if all the required parameters are present."""
        if self.request.method != 'POST':
            self.write_error(status_code=405, message="Method not allowed")
            return

        self.parameters = dict()
        for key in get_arguments():
            try:
                self.parameters[key] = self.get_argument(key)
            except MissingArgumentError:
                self.write_error(status_code=400,
                                message="Missing Argument(s)")
                return

        # checking if 'label' is provided
        if 'label' in self.request.arguments.keys():
            self.parameters['label'] = self.get_argument('label')
        # Set up response dictionary.
        self.response = dict()

    def get(self, *args, **kwargs):
        self.write_error(status_code=405, message="Method not allowed")

    def post(self, *args, **kwargs):
        """Executes the main logic part."""
        self.response = get_amort_schedule(self.parameters)
        self.write_json()

    def set_default_headers(self):
        """Sets content-type as 'application/json' for response as JSON."""
        self.set_header('Content-Type', 'application/json')

    def write_error(self, status_code, **kwargs):
        """Invokes when error occurs in processing the request."""
        if 'message' not in kwargs:
            if status_code == 405:
                kwargs['message'] = 'Invalid HTTP method.'
            else:
                kwargs['message'] = 'Unknown error.'
        kwargs["error"] = True
        self.set_status(status_code=status_code)
        self.response = dict(kwargs)
        self.write_json()

    def write_json(self):
        """Responsible for writing the response."""
        if "status" in self.response:
            self.set_status(self.response.get("status"))
        self.set_default_headers()
        self.write(self.response)
        self.finish()


def main():
    app = tornado.web.Application([
        (r'/', MainHandler),
    ], debug=True)
    # server = HTTPServer(app)
    # server.bind(8888)
    # server.start(0)
    app.listen()
    # app.run(host='0.0.0.0')
    IOLoop.current().start()


if __name__ == '__main__':
    main()

What is the mistake here and how can I fix it?


Solution

  • It looks like the deployment is succeeding, but when Zappa checks to see if the code is working, the return code is 502, which suggests that the lambda function is failing to run in the lambda environment.

    Taking a look at the logs, the critical line is:

    AttributeError: module 'main' has no attribute 'app'

    And this is true, if we look at your code, at no point do you expose an attribute called app in main.py.

    I'm not experienced with Tornado, but I suspect that if you move the declaration of app out of the main() function and into the root scope, then the handler should succeed.

    For example:

            # rest of the file...
            self.finish()
    
    app = tornado.web.Application([
        (r'/', MainHandler),
    ], debug=True)
    app.listen()
    
    def main():
        IOLoop.current().start()
    
    if __name__ == '__main__':
        main()