forked from serverless/serverless
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserverless-run-python-handler
executable file
·195 lines (152 loc) · 5.35 KB
/
serverless-run-python-handler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/usr/bin/env python2.7
from __future__ import print_function
import argparse
import StringIO
import traceback
import contextlib
import imp
import json
import os
import sys
parser = argparse.ArgumentParser(
prog='run_handler',
description='Runs a Lambda entry point (handler) with an optional event',
)
parser.add_argument(
'--event', dest='event',
type=json.loads,
help=("The event that will be deserialized and passed to the function. "
"This has to be valid JSON, and will be deserialized into a "
"Python dictionary before your handler is invoked")
)
parser.add_argument(
'--handler-path', dest='handler_path',
help=("File path to the handler, e.g. `lib/function.py`")
)
parser.add_argument(
'--handler-function', dest='handler_function',
default='lambda_handler',
help=("File path to the handler")
)
class FakeLambdaContext(object):
def __init__(self, name='Fake', version='LATEST'):
self.name = name
self.version = version
@property
def get_remaining_time_in_millis(self):
return 10000
@property
def function_name(self):
return self.name
@property
def function_version(self):
return self.version
@property
def invoked_function_arn(self):
return 'arn:aws:lambda:serverless:' + self.name
@property
def memory_limit_in_mb(self):
return 1024
@property
def aws_request_id(self):
return '1234567890'
@contextlib.contextmanager
def preserve_value(namespace, name):
"""A context manager to restore a binding to its prior value
At the beginning of the block, `__enter__`, the value specified is
saved, and is restored when `__exit__` is called on the contextmanager
namespace (object): Some object with a binding
name (string): The name of the binding to be preserved.
"""
saved_value = getattr(namespace, name)
yield
setattr(namespace, name, saved_value)
@contextlib.contextmanager
def capture_fds(stdout=None, stderr=None):
"""Replace stdout and stderr with a different file handle.
Call with no arguments to just ignore stdout or stderr.
"""
orig_stdout, orig_stderr = sys.stdout, sys.stderr
orig_stdout.flush()
orig_stderr.flush()
temp_stdout = stdout or StringIO.StringIO()
temp_stderr = stderr or StringIO.StringIO()
sys.stdout, sys.stderr = temp_stdout, temp_stderr
yield
sys.stdout = orig_stdout
sys.stderr = orig_stderr
temp_stdout.flush()
temp_stdout.seek(0)
temp_stderr.flush()
temp_stderr.seek(0)
def make_module_from_file(module_name, module_filepath):
"""Make a new module object from the source code in specified file.
:param module_name: Desired name (must be valid import name)
:param module_filepath: The filesystem path with the Python source
:return: A loaded module
The Python import mechanism is not used. No cached bytecode
file is created, and no entry is placed in `sys.modules`.
"""
py_source_open_mode = 'U'
py_source_description = (".py", py_source_open_mode, imp.PY_SOURCE)
with open(module_filepath, py_source_open_mode) as module_file:
with preserve_value(sys, 'dont_write_bytecode'):
sys.dont_write_bytecode = True
module = imp.load_module(
module_name,
module_file,
module_filepath,
py_source_description
)
return module
def bail_out(code=99):
output = {
'success': False,
'exception': traceback.format_exception(*sys.exc_info()),
}
print(json.dumps(output))
sys.exit(code)
def import_program_as_module(handler_file):
"""Import module from `handler_file` and return it to be used.
Since we don't want to clutter up the filesystem, we're going to turn
off bytecode generation (.pyc file creation)
"""
module = make_module_from_file('lambda_handler', handler_file)
sys.modules['lambda_handler'] = module
return module
def run_with_context(handler, function_path, event=None):
function = getattr(handler, function_path)
return function(event or {}, FakeLambdaContext())
if __name__ == '__main__':
args = parser.parse_args(sys.argv[1:])
path = os.path.expanduser(args.handler_path)
if not os.path.isfile(path):
message = (u'There is no such file "{}". --handler-path must be a '
u'Python file'.format(path))
print(json.dumps({"success": False, "exception": message}))
sys.exit(100)
try:
handler = import_program_as_module(path)
except Exception as e:
bail_out()
stdout, stderr = StringIO.StringIO(), StringIO.StringIO()
output = {}
with capture_fds(stdout, stderr):
try:
result = run_with_context(handler, args.handler_function, args.event)
output['result'] = result
except Exception as e:
message = u'Failure running handler function {f} from file {file}:\n{tb}'
output['exception'] = message.format(
f=args.handler_function,
file=path,
tb=traceback.format_exception(*sys.exc_info()),
)
output['success'] = False
else:
output['success'] = True
output.update({
'stdout': stdout.read(),
'stderr': stderr.read(),
})
print(json.dumps(output))