Skip to content

Commit 901290d

Browse files
authored
Fix Windows support (GoogleCloudPlatform#40)
* Test on multiple platforms * Don't install gunicorn on Windows * Fall back on Flask when Gunicorn is missing * Update changelog * Remove travis config
1 parent f20e114 commit 901290d

File tree

11 files changed

+175
-84
lines changed

11 files changed

+175
-84
lines changed

.github/workflows/main.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: CI
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
7+
lint:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v2
11+
- name: Setup Python
12+
uses: actions/setup-python@v1
13+
with:
14+
python-version: 3.8
15+
- name: Install tox
16+
run: python -m pip install tox
17+
- name: Run linting
18+
run: python -m tox -e lint
19+
20+
test:
21+
strategy:
22+
matrix:
23+
python: [3.6, 3.7, 3.8]
24+
platform: [ubuntu-latest, macos-latest, windows-latest]
25+
runs-on: ${{ matrix.platform }}
26+
steps:
27+
- uses: actions/checkout@v2
28+
- name: Setup Python
29+
uses: actions/setup-python@v1
30+
with:
31+
python-version: ${{ matrix.python }}
32+
- name: Install tox
33+
run: python -m pip install tox
34+
- name: Run tests
35+
run: python -m tox -e py-${{ matrix.platform }}

.travis.yml

-18
This file was deleted.

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
- Fix Windows support ([#38])
89

910
## [1.4.0] - 2020-05-06
1011
- Use gunicorn as a production HTTP server
@@ -50,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5051
[1.0.1]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.0.1
5152
[1.0.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.0.0
5253

54+
[#38]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/38
5355
[#33]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/33
5456
[#31]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/31
5557
[#20]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/20

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"flask>=1.0,<2.0",
5252
"click>=7.0,<8.0",
5353
"watchdog>=0.10.0",
54-
"gunicorn>=19.2.0,<21.0",
54+
"gunicorn>=19.2.0,<21.0; platform_system!='Windows'",
5555
],
5656
extras_require={"test": ["pytest", "tox"]},
5757
entry_points={

src/functions_framework/_cli.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,5 @@ def _cli(target, source, signature_type, host, port, debug, dry_run):
3939
click.echo("Function: {}".format(target))
4040
click.echo("URL: http://{}:{}/".format(host, port))
4141
click.echo("Dry run successful, shutting down.")
42-
elif debug:
43-
# Run with Flask's development WSGI server
44-
app.run(host, port, debug)
4542
else:
46-
# Run with Gunicorn's production WSGI server
47-
create_server(app).run(host, port)
43+
create_server(app, debug).run(host, port)
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain 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,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from functions_framework._http.flask import FlaskApplication
16+
17+
18+
class HTTPServer:
19+
def __init__(self, app, debug, **options):
20+
self.app = app
21+
self.options = options
22+
23+
if debug:
24+
self.server_class = FlaskApplication
25+
else:
26+
try:
27+
from functions_framework._http.gunicorn import GunicornApplication
28+
29+
self.server_class = GunicornApplication
30+
except ImportError as e:
31+
self.server_class = FlaskApplication
32+
33+
def run(self, host, port):
34+
http_server = self.server_class(self.app, host, port, **self.options)
35+
http_server.run()
36+
37+
38+
def create_server(wsgi_app, debug, **options):
39+
return HTTPServer(wsgi_app, debug, **options)
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain 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,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
class FlaskApplication:
17+
def __init__(self, app, host, port, **options):
18+
self.app = app
19+
self.host = host
20+
self.port = port
21+
self.options = options
22+
23+
def run(self):
24+
self.app.run(self.host, self.port, debug=True, **self.options)

src/functions_framework/_http.py renamed to src/functions_framework/_http/gunicorn.py

-15
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,3 @@ def load_config(self):
3333

3434
def load(self):
3535
return self.app
36-
37-
38-
class HTTPServer:
39-
def __init__(self, app, server_class, **options):
40-
self.app = app
41-
self.server_class = server_class
42-
self.options = options
43-
44-
def run(self, host, port):
45-
http_server = self.server_class(self.app, host, port, **self.options)
46-
http_server.run()
47-
48-
49-
def create_server(wsgi_app, **options):
50-
return HTTPServer(wsgi_app, server_class=GunicornApplication, **options)

tests/test_cli.py

+5-19
Original file line numberDiff line numberDiff line change
@@ -31,90 +31,77 @@ def test_cli_no_arguments():
3131

3232

3333
@pytest.mark.parametrize(
34-
"args, env, create_app_calls, app_run_calls, wsgi_server_run_calls",
34+
"args, env, create_app_calls, run_calls",
3535
[
3636
(
3737
["--target", "foo"],
3838
{},
3939
[pretend.call("foo", None, "http")],
40-
[],
4140
[pretend.call("0.0.0.0", 8080)],
4241
),
4342
(
4443
[],
4544
{"FUNCTION_TARGET": "foo"},
4645
[pretend.call("foo", None, "http")],
47-
[],
4846
[pretend.call("0.0.0.0", 8080)],
4947
),
5048
(
5149
["--target", "foo", "--source", "/path/to/source.py"],
5250
{},
5351
[pretend.call("foo", "/path/to/source.py", "http")],
54-
[],
5552
[pretend.call("0.0.0.0", 8080)],
5653
),
5754
(
5855
[],
5956
{"FUNCTION_TARGET": "foo", "FUNCTION_SOURCE": "/path/to/source.py"},
6057
[pretend.call("foo", "/path/to/source.py", "http")],
61-
[],
6258
[pretend.call("0.0.0.0", 8080)],
6359
),
6460
(
6561
["--target", "foo", "--signature-type", "event"],
6662
{},
6763
[pretend.call("foo", None, "event")],
68-
[],
6964
[pretend.call("0.0.0.0", 8080)],
7065
),
7166
(
7267
[],
7368
{"FUNCTION_TARGET": "foo", "FUNCTION_SIGNATURE_TYPE": "event"},
7469
[pretend.call("foo", None, "event")],
75-
[],
7670
[pretend.call("0.0.0.0", 8080)],
7771
),
7872
(
7973
["--target", "foo", "--dry-run"],
8074
{},
8175
[pretend.call("foo", None, "http")],
8276
[],
83-
[],
8477
),
8578
(
8679
[],
8780
{"FUNCTION_TARGET": "foo", "DRY_RUN": "True"},
8881
[pretend.call("foo", None, "http")],
8982
[],
90-
[],
9183
),
9284
(
9385
["--target", "foo", "--host", "127.0.0.1"],
9486
{},
9587
[pretend.call("foo", None, "http")],
96-
[],
9788
[pretend.call("127.0.0.1", 8080)],
9889
),
9990
(
10091
["--target", "foo", "--debug"],
10192
{},
10293
[pretend.call("foo", None, "http")],
103-
[pretend.call("0.0.0.0", 8080, True)],
104-
[],
94+
[pretend.call("0.0.0.0", 8080)],
10595
),
10696
(
10797
[],
10898
{"FUNCTION_TARGET": "foo", "DEBUG": "True"},
10999
[pretend.call("foo", None, "http")],
110-
[pretend.call("0.0.0.0", 8080, True)],
111-
[],
100+
[pretend.call("0.0.0.0", 8080)],
112101
),
113102
],
114103
)
115-
def test_cli(
116-
monkeypatch, args, env, create_app_calls, app_run_calls, wsgi_server_run_calls,
117-
):
104+
def test_cli(monkeypatch, args, env, create_app_calls, run_calls):
118105
wsgi_server = pretend.stub(run=pretend.call_recorder(lambda *a, **kw: None))
119106
wsgi_app = pretend.stub(run=pretend.call_recorder(lambda *a, **kw: None))
120107
create_app = pretend.call_recorder(lambda *a, **kw: wsgi_app)
@@ -127,5 +114,4 @@ def test_cli(
127114

128115
assert result.exit_code == 0
129116
assert create_app.calls == create_app_calls
130-
assert wsgi_app.run.calls == app_run_calls
131-
assert wsgi_server.run.calls == wsgi_server_run_calls
117+
assert wsgi_server.run.calls == run_calls

0 commit comments

Comments
 (0)