Skip to content

Commit fc6de06

Browse files
authored
Adding Py3.5.2+ support (#70)
In order to make FDL work with Python 3.5.2+ the following changes needed: - "start_serving" available from 3.7.1, so, i had to do some plumbing and version checks - importlib available also from 3.7.1, but it was available before but with different API, as for now it is considered as deprecated in favour of "importlib" Closes: #69
1 parent 1d12394 commit fc6de06

5 files changed

Lines changed: 152 additions & 20 deletions

File tree

fdk/async_http/server.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
from .protocol import HttpProtocol
2727

28+
from fdk import constants
29+
2830

2931
class Signal(object):
3032
stopped = False
@@ -149,13 +151,20 @@ def serve(
149151
debug=debug,
150152
)
151153

152-
server_coroutine = loop.create_server(
153-
server,
154+
create_server_kwargs = dict(
154155
ssl=ssl,
155156
reuse_port=reuse_port,
156157
sock=sock,
157158
backlog=backlog,
158-
start_serving=False
159+
)
160+
161+
if constants.is_py37():
162+
create_server_kwargs.update(
163+
start_serving=False
164+
)
165+
166+
server_coroutine = loop.create_server(
167+
server, **create_server_kwargs
159168
)
160169

161170
# Instead of pulling time at the end of every request,
@@ -188,14 +197,17 @@ def serve(
188197
)
189198

190199
def start_serving():
191-
loop.run_until_complete(http_server.start_serving())
200+
if constants.is_py37():
201+
loop.run_until_complete(http_server.start_serving())
192202

193203
def start():
194204
pid = os.getpid()
195205
try:
196206
logger.info("Starting worker [%s]", pid)
197-
loop.run_until_complete(http_server.serve_forever())
198-
# loop.run_forever()
207+
if constants.is_py37():
208+
loop.run_until_complete(http_server.serve_forever())
209+
else:
210+
loop.run_forever()
199211
finally:
200212
http_server.close()
201213
loop.run_until_complete(http_server.wait_closed())

fdk/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414

15+
import sys
16+
1517
ASYNC_IO_READ_BUFFER = 65536
1618
DEFAULT_DEADLINE = 30
1719
HTTPSTREAM = "http-stream"
@@ -29,3 +31,9 @@
2931
CONTENT_LENGTH = "Content-Length"
3032
FN_ENFORCED_RESPONSE_CODES = [200, 502, 504]
3133
FN_DEFAULT_RESPONSE_CODE = 200
34+
35+
36+
# todo: python 3.8 is on its way, make more flexible
37+
def is_py37():
38+
py_version = sys.version_info
39+
return (py_version.major, py_version.minor) == (3, 7)

fdk/customer_code.py

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,81 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414

15+
import os
1516

16-
class Function(object):
17+
from fdk import constants
1718

18-
def __init__(self, func_module_path, entrypoint="handler"):
19-
self.spec, self.mod = self.import_from_source(func_module_path)
19+
20+
def get_delayed_module_init_class():
21+
if constants.is_py37():
22+
return Python37DelayedImport
23+
else:
24+
return Python35plusDelayedImport
25+
26+
27+
class PythonDelayedImportAbstraction(object):
28+
29+
def __init__(self, func_module_path):
30+
self._mod_path = func_module_path
2031
self._executed = False
21-
self._entrypoint = entrypoint
2232

23-
@staticmethod
24-
def import_from_source(func_module_path):
33+
@property
34+
def executed(self):
35+
return self._executed
36+
37+
@executed.setter
38+
def executed(self, exec_flag):
39+
self._executed = exec_flag
40+
41+
def get_module(self):
42+
raise Exception("Not implemented")
43+
44+
45+
class Python35plusDelayedImport(PythonDelayedImportAbstraction):
46+
47+
def __init__(self, func_module_path):
48+
self._func_module = None
49+
super(Python35plusDelayedImport, self).__init__(func_module_path)
50+
51+
def get_module(self):
52+
if not self.executed:
53+
import imp
54+
fname, ext = os.path.splitext(
55+
os.path.basename(self._mod_path))
56+
self._func_module = imp.load_source(fname, self._mod_path)
57+
self.executed = True
58+
59+
return self._func_module
60+
61+
62+
class Python37DelayedImport(PythonDelayedImportAbstraction):
63+
64+
def import_from_source(self):
2565
from importlib import util
2666
func_module_spec = util.spec_from_file_location(
27-
"func", func_module_path
67+
"func", self._mod_path
2868
)
2969
func_module = util.module_from_spec(func_module_spec)
30-
return func_module_spec, func_module
70+
self._func_module_spec = func_module_spec
71+
self._func_module = func_module
72+
73+
def get_module(self):
74+
if not self.executed:
75+
self.import_from_source()
76+
self._func_module_spec.loader.exec_module(
77+
self._func_module)
78+
self.executed = True
79+
80+
return self._func_module
3181

32-
def handler(self):
33-
if self._executed is False:
34-
self.spec.loader.exec_module(self.mod)
35-
self._executed = True
3682

37-
return getattr(self.mod, self._entrypoint)
83+
class Function(object):
84+
85+
def __init__(self, func_module_path, entrypoint="handler"):
86+
dm = get_delayed_module_init_class()
87+
self._delayed_module_class = dm(func_module_path)
88+
self._entrypoint = entrypoint
89+
90+
def handler(self):
91+
mod = self._delayed_module_class.get_module()
92+
return getattr(mod, self._entrypoint)

fdk/tests/test_delayed_loader.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
import pytest
16+
17+
from fdk import constants
18+
from fdk import customer_code
19+
20+
from fdk.tests import funcs
21+
22+
23+
@pytest.mark.skipif(constants.is_py37(),
24+
reason="this test is for Python 3.5.2+ only")
25+
def test_py352plus():
26+
dm = customer_code.Python35plusDelayedImport(funcs.__file__)
27+
assert dm.executed is False
28+
29+
m = dm.get_module()
30+
assert dm.executed is True
31+
assert m is not None
32+
33+
34+
@pytest.mark.skipif(not constants.is_py37(),
35+
reason="this test is for Python 3.7.1+ only")
36+
def test_py37():
37+
dm = customer_code.Python37DelayedImport(funcs.__file__)
38+
assert dm.executed is False
39+
40+
m = dm.get_module()
41+
assert dm.executed is True
42+
assert m is not None
43+
44+
45+
def test_generic_delayed_loader():
46+
f = customer_code.Function(
47+
funcs.__file__, entrypoint="content_type")
48+
assert f is not None
49+
assert f._delayed_module_class.executed is False
50+
51+
h = f.handler()
52+
assert h is not None
53+
assert f._delayed_module_class.executed is True

tox.ini

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[tox]
2-
envlist = py{3.7},pep8
2+
envlist = py{3.7,3.6},pep8
33
skipsdist = True
44

55
[testenv]
66
basepython =
77
pep8: python3.7
88
py3.7: python3.7
9+
py3.6: python3.6
910

1011
passenv =
1112
DOCKER_HOST
@@ -29,6 +30,9 @@ commands = {posargs}
2930
[testenv:py3.7]
3031
commands = pytest -v -s --tb=long --cov=fdk {toxinidir}/fdk/tests
3132

33+
[testenv:py3.6]
34+
commands = pytest -v -s --tb=long --cov=fdk {toxinidir}/fdk/tests
35+
3236
[flake8]
3337
ignore = H405,H404,H403,H401,H306,H304,H101,E303,H301,W503
3438
show-source = True

0 commit comments

Comments
 (0)