Skip to content

Instantly share code, notes, and snippets.

@jeznag
Created October 2, 2020 05:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jeznag/8391d2ada2cf1c6a6e452fd6e3a52544 to your computer and use it in GitHub Desktop.
Save jeznag/8391d2ada2cf1c6a6e452fd6e3a52544 to your computer and use it in GitHub Desktop.
lambda slim handler
from __future__ import unicode_literals
import boto3
import importlib
import inspect
import json
import logging
import os
import sys
import tarfile
from builtins import str
PROJECT_FOLDER = '/tmp/mega_module'
# Set up logging
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.debug("start handler")
class LambdaHandler(object):
"""
Singleton for avoiding duplicate setup.
Pattern provided by @benbangert.
"""
__instance = None
session = None
# Application
app_module = None
def __new__(cls):
"""Singleton instance to avoid repeat setup"""
if LambdaHandler.__instance is None:
if sys.version_info[0] < 3:
LambdaHandler.__instance = object.__new__(
cls)
else:
LambdaHandler.__instance = object.__new__(cls)
return LambdaHandler.__instance
def __init__(self):
self.load_remote_project_archive()
def download_and_extract_tarball_from_s3(self, bucket_name, bucket_key, folder):
if not self.session:
boto_session = boto3.Session()
else:
boto_session = self.session
s3 = boto_session.resource('s3')
archive_on_s3 = s3.Object(bucket_name, bucket_key).get()
tar = tarfile.open(fileobj=S3ObjectWithTell(archive_on_s3['Body']))
tar.extractall(folder)
tar.close()
def load_remote_project_archive(self):
"""
Puts the project files from S3 in /tmp and adds to path
"""
if not os.path.isdir(PROJECT_FOLDER):
# The project folder doesn't exist in this cold lambda, get it from S3
self.download_and_extract_tarball_from_s3(os.environ['MODEL_LAMBDA_DEPENDENCIES_BUCKET_NAME'], os.environ['MODEL_LAMBDA_DEPENDENCIES_KEY'], PROJECT_FOLDER)
self.download_and_extract_tarball_from_s3(os.environ['MODEL_LAMBDA_BUCKET_NAME'], os.environ['MODEL_LAMBDA_KEY'], f"{PROJECT_FOLDER}/build")
sys.path.insert(0, f"{PROJECT_FOLDER}/build")
os.chdir(f"{PROJECT_FOLDER}/build")
return True
@staticmethod
def import_module_and_get_function():
"""
Given a modular path to a function, import that module
and return the function.
"""
module = f"{os.environ['MODEL_BUILDER_MODULE']}"
function = os.environ['MODEL_BUILDER_FUNCTION']
app_module = importlib.import_module(module)
app_function = getattr(app_module, function)
return app_function
@classmethod
def lambda_handler(cls, event, context): # pragma: no cover
handler = cls()
try:
print("Running handler")
return handler.handler(event, context)
except Exception as ex:
logging.exception("something bad happened")
logging.exception(ex)
raise(ex)
@staticmethod
def run_function(app_function, event, context):
"""
Given a function and event context,
detect signature and execute, returning any result.
"""
# getargspec does not support python 3 method with type hints
# Related issue: https://github.com/Miserlou/Zappa/issues/1452
if hasattr(inspect, "getfullargspec"): # Python 3
args, varargs, keywords, defaults, _, _, _ = inspect.getfullargspec(
app_function)
else: # Python 2
args, varargs, keywords, defaults = inspect.getargspec(
app_function)
num_args = len(args)
if num_args == 0:
result = app_function(
event, context) if varargs else app_function()
elif num_args == 1:
result = app_function(
event, context) if varargs else app_function(event)
elif num_args == 2:
result = app_function(event, context)
else:
raise RuntimeError("Function signature is invalid. Expected a function that accepts at most "
"2 arguments or varargs.")
return result
def handler(self, event, context):
"""
An AWS Lambda function which parses specific API Gateway input into a
WSGI request, feeds it to our WSGI app, procceses the response, and returns
that back to the API Gateway.
"""
# This is an AWS-event triggered invocation.
if event.get('Records', None):
records = event.get('Records')
result = None
app_function = self.import_module_and_get_function()
result = self.run_function(app_function, event, context)
logger.debug(result)
return result
class S3ObjectWithTell:
def __init__(self, s3object):
self.s3object = s3object
self.offset = 0
def read(self, amount=None):
result = self.s3object.read(amount)
self.offset += len(result)
return result
def close(self):
self.s3object.close()
def tell(self):
return self.offset
def lambda_handler(event, context): # pragma: no cover
return LambdaHandler.lambda_handler(event, context)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment