Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Commit

Permalink
Merge pull request #74 from sernst/73-add-waitress
Browse files Browse the repository at this point in the history
Waitress Serving
  • Loading branch information
sernst committed Sep 26, 2020
2 parents a08cf46 + fa34c82 commit 09bcbc3
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 64 deletions.
37 changes: 27 additions & 10 deletions .gitlab-ci.yml
Expand Up @@ -10,6 +10,7 @@ pytest-py353:
- pip install -r requirements.txt
- >
py.test
--verbose
--cov-report term
--cov=cauldron
.
Expand All @@ -22,6 +23,7 @@ pytest-py35:
- pip install -r requirements.txt
- >
py.test
--verbose
--cov-report term
--cov=cauldron
.
Expand All @@ -34,58 +36,73 @@ pytest-py36:
- pip install -r requirements.txt
- >
py.test
--verbose
--cov-report term
--cov=cauldron
.
pytest-py37:
image: python:3.7
stage: check
coverage: '/^TOTAL.*\s+\d+\s+\d+\s+(\d+)%/'
script:
- export PYTHONPATH="${PYTHONPATH}:$(pwd)"
- pip install -r requirements.txt
- pip install codecov coveralls codacy-coverage
- >
py.test
--verbose
--cov-report xml:"$(pwd)/coverage.xml"
--cov-report term
--cov=cauldron
.
pytest-py38:
image: python:3.8
stage: check
coverage: '/^TOTAL.*\s+\d+\s+\d+\s+(\d+)%/'
script:
- export PYTHONPATH="${PYTHONPATH}:$(pwd)"
- pip install -r requirements.txt
- >
py.test
--verbose
--cov-report term
--cov=cauldron
.
artifacts:
paths:
- .coverage
- coverage.xml
expire_in: 1 day

pytest-py38:
image: python:3.8
pytest-py39:
image: python:3.9.0rc2
stage: check
# It takes too long to build everything from source at this point
# so this is disabled until wheels start appearing.
when: manual
allow_failure: true
script:
- export PYTHONPATH="${PYTHONPATH}:$(pwd)"
- pip install -r requirements.txt
- >
py.test
--verbose
--cov-report term
--cov=cauldron
.
codecov:
image: python:3.7
image: python:3.8
stage: broadcast
script:
- pip install codecov
- ls -la
- codecov

coveralls:
image: python:3.7
image: python:3.8
stage: broadcast
# Coveralls does not currently support coverage.py 5.0+ and
# so until that time, this should be allowed to fail so that
# it does not fail the pipeline. See issue for details:
# https://github.com/coveralls-clients/coveralls-python/issues/203
allow_failure: true
script:
- pip install coveralls
- ls -la
Expand Down
22 changes: 0 additions & 22 deletions .travis.yml

This file was deleted.

18 changes: 17 additions & 1 deletion cauldron/cli/server/run.py
Expand Up @@ -6,6 +6,7 @@
from argparse import ArgumentParser

from flask import Flask
import waitress

import cauldron as cd
from cauldron import environ
Expand Down Expand Up @@ -124,6 +125,17 @@ def create_parser(arg_parser: ArgumentParser = None) -> ArgumentParser:
default=None
)

parser.add_argument(
'--basic',
action='store_true',
help="""
When specified a basic Flask server will be used to
serve the kernel instead of a waitress WSGI server.
Use only when necessary as the Flask server isn't
as robust.
"""
)

return parser


Expand Down Expand Up @@ -182,5 +194,9 @@ def execute(
**kwargs
)
app = populated_server_data['application']
app.run(port=port, debug=debug,host=host)
if kwargs.get('basic'):
app.run(port=port, debug=debug, host=host)
else:
waitress.serve(app, port=port, host=host or 'localhost')

environ.modes.remove(environ.modes.INTERACTIVE)
2 changes: 1 addition & 1 deletion cauldron/settings.json
@@ -1,4 +1,4 @@
{
"version": "1.0.4",
"version": "1.0.5",
"notebookVersion": "v1"
}
34 changes: 21 additions & 13 deletions cauldron/test/cli/server/test_server.py
Expand Up @@ -77,19 +77,6 @@ def test_abort_invalid(self):
aborted = self.get('/abort')
self.assert_no_errors(aborted.response)

def test_start_server(self):
"""Should start server with specified settings."""

kwargs = dict(
port=9999,
debug=True,
host='TEST'
)

with mock.patch('cauldron.cli.server.run.APPLICATION.run') as func:
server_run.execute(**kwargs)
func.assert_called_once_with(**kwargs)

def test_start_server_version(self):
"""Should return server version without starting the server."""

Expand Down Expand Up @@ -154,3 +141,24 @@ def test_long_running_print_buffering(self):

aborted = self.get('/abort')
self.assert_no_errors(aborted.response)

@mock.patch('cauldron.cli.server.run.APPLICATION.run')
def test_start_server_basic(self, application_run: mock.MagicMock):
"""Should start server with specified settings."""
kwargs = dict(
port=9999,
debug=True,
host='TEST',
)
server_run.execute(basic=True, **kwargs)
application_run.assert_called_once_with(**kwargs)

@mock.patch('cauldron.cli.server.run.waitress.serve')
def test_start_server(self, waitress_serve: mock.MagicMock):
"""Should start server with specified settings."""
kwargs = dict(
port=9999,
host='TEST',
)
server_run.execute(**kwargs)
assert waitress_serve.call_args[1] == kwargs
80 changes: 71 additions & 9 deletions cauldron/test/ui/test_ui_start.py
Expand Up @@ -11,7 +11,9 @@
@patch('cauldron.ui.environ.systems')
@patch('cauldron.ui.configs')
@patch('flask.Flask')
@patch('waitress.serve')
def test_start_defaults(
waitress_serve: MagicMock,
flask_constructor: MagicMock,
ui_configs: MagicMock,
environ_systems: MagicMock,
Expand All @@ -30,10 +32,11 @@ def test_start_defaults(
flask_constructor.return_value = app
ui.start()

expected = {'port': 1234, 'debug': False, 'host': None}
assert {'threaded': True, **expected} == app.run.call_args[1], """
expected = {'port': 1234, 'host': 'localhost'}
assert expected == waitress_serve.call_args[1], """
Expect app run configuration to be {}
""".format(expected)
expected = {'port': 1234, 'host': None}
assert all(
item in ui_configs.UI_APP_DATA.items()
for item in expected.items()
Expand All @@ -52,7 +55,7 @@ def test_start_defaults(
@patch('cauldron.ui.environ.systems')
@patch('cauldron.ui.configs')
@patch('flask.Flask')
def test_start_customized(
def test_start_customized_basic(
flask_constructor: MagicMock,
ui_configs: MagicMock,
environ_systems: MagicMock,
Expand All @@ -69,7 +72,7 @@ def test_start_customized(

app = MagicMock()
flask_constructor.return_value = app
ui.start(port=4321, debug=True, public=True)
ui.start(port=4321, debug=True, public=True, basic=True)

expected = {'port': 4321, 'debug': True, 'host': '0.0.0.0'}
assert {'threaded': True, **expected} == app.run.call_args[1], """
Expand All @@ -96,7 +99,53 @@ def test_start_customized(
@patch('cauldron.ui.environ.systems')
@patch('cauldron.ui.configs')
@patch('flask.Flask')
def test_start_remote_connection(
@patch('waitress.serve')
def test_start_customized(
waitress_serve: MagicMock,
flask_constructor: MagicMock,
ui_configs: MagicMock,
environ_systems: MagicMock,
remote_connection: MagicMock,
connect: MagicMock,
launcher: MagicMock,
):
"""Should start the ui with customized configuration."""
ui_configs.UI_APP_DATA = {}
ui_configs.LAUNCH_THREAD = None
connect._clean_url.return_value = 'foo'
connect.check_connection.return_value = environ.Response().fail().response
launcher.find_open_port.return_value = 1234

app = MagicMock()
flask_constructor.return_value = app
ui.start(port=4321, public=True, debug=True)

expected = {'port': 4321, 'host': '0.0.0.0'}
assert expected == waitress_serve.call_args[1], """
Expect app run configuration to be {}
""".format(expected)
assert all(
item in ui_configs.UI_APP_DATA.items()
for item in expected.items()
), """
Expect configs.UI_APP_DATA to have {}
""".format(expected)
assert 0 == environ_systems.end.call_count, """
Expected no call to end the application execution process.
"""
assert ui_configs.LAUNCH_THREAD is None, """
Expect no launch thread when run in debug mode because
auto-reloading causes problems.
"""


@patch('cauldron.ui.launcher')
@patch('cauldron.ui.connect')
@patch('cauldron.ui.environ.remote_connection')
@patch('cauldron.ui.environ.systems')
@patch('cauldron.ui.configs')
@patch('flask.Flask')
def test_start_remote_connection_basic(
flask_constructor: MagicMock,
ui_configs: MagicMock,
environ_systems: MagicMock,
Expand All @@ -113,7 +162,13 @@ def test_start_remote_connection(

app = MagicMock()
flask_constructor.return_value = app
ui.start(port=4321, debug=True, host='bar', connection_url='foo:8080')
ui.start(
port=4321,
debug=True,
host='bar',
connection_url='foo:8080',
basic=True,
)

expected = {'port': 4321, 'debug': True, 'host': 'bar'}
assert {'threaded': True, **expected} == app.run.call_args[1], """
Expand Down Expand Up @@ -142,7 +197,7 @@ def test_start_remote_connection(
@patch('cauldron.ui.environ.systems')
@patch('cauldron.ui.configs')
@patch('flask.Flask')
def test_start_remote_connection_failed(
def test_start_remote_connection_failed_basic(
flask_constructor: MagicMock,
ui_configs: MagicMock,
environ_systems: MagicMock,
Expand All @@ -159,7 +214,13 @@ def test_start_remote_connection_failed(

app = MagicMock()
flask_constructor.return_value = app
ui.start(port=4321, debug=True, host='bar', connection_url='foo:8080')
ui.start(
port=4321,
debug=True,
host='bar',
connection_url='foo:8080',
basic=True,
)

assert 0 == app.run.call_count, 'Expect no application to start.'
assert (1,) == environ_systems.end.call_args[0], """
Expand All @@ -174,7 +235,9 @@ def test_start_remote_connection_failed(
@patch('cauldron.ui.environ.systems')
@patch('cauldron.ui.configs')
@patch('flask.Flask')
@patch('waitress.serve')
def test_start_version(
waitress_serve: MagicMock,
flask_constructor: MagicMock,
ui_configs: MagicMock,
environ_systems: MagicMock,
Expand All @@ -193,7 +256,6 @@ def test_start_version(
flask_constructor.return_value = app
ui.start(version=True)

assert 0 == app.run.call_count, 'Expect no application to start.'
assert (0,) == environ_systems.end.call_args[0], """
Expected exit to be called one with a zero returncode.
"""
20 changes: 14 additions & 6 deletions cauldron/ui/__init__.py
@@ -1,6 +1,7 @@
import logging

import flask
import waitress

from cauldron import environ
from cauldron import templating
Expand Down Expand Up @@ -113,12 +114,19 @@ def start(
)

app = ui_app_data['application']
app.run(
port=ui_app_data['port'],
debug=ui_app_data['debug'],
host=ui_app_data['host'],
threaded=True
)
if kwargs.get('basic'):
app.run(
port=ui_app_data['port'],
debug=ui_app_data['debug'],
host=ui_app_data['host'],
threaded=True
)
else:
waitress.serve(
app,
host=ui_app_data['host'] or 'localhost',
port=ui_app_data['port'],
)

environ.modes.remove(environ.modes.UI)
if not ui_app_data['was_interactive']:
Expand Down

0 comments on commit 09bcbc3

Please sign in to comment.