Merge branch 'staging' into 'master'
Pagelet Initialization See merge request s3lph/matemat!38
This commit is contained in:
commit
45c0237e7f
8 changed files with 159 additions and 6 deletions
|
@ -10,8 +10,9 @@ test:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- pip3 install -r requirements.txt
|
- pip3 install -r requirements.txt
|
||||||
- sudo -u matemat python3 -m coverage run --branch -m unittest discover matemat
|
- sudo -u matemat python3 -m coverage run --rcfile=setup.cfg -m unittest discover matemat
|
||||||
- sudo -u matemat python3 -m coverage report -m --include 'matemat/*' --omit '*/test_*.py'
|
- sudo -u matemat python3 -m coverage combine
|
||||||
|
- sudo -u matemat python3 -m coverage report --rcfile=setup.cfg
|
||||||
|
|
||||||
codestyle:
|
codestyle:
|
||||||
stage: test
|
stage: test
|
||||||
|
|
2
doc
2
doc
|
@ -1 +1 @@
|
||||||
Subproject commit 9449a6dc39843969d3b549f05848b5857c23cfa3
|
Subproject commit 0cf3d59c8b37f84e915f5e30e7447f0611cc1238
|
|
@ -8,5 +8,5 @@ server will attempt to serve the request with a static resource in a previously
|
||||||
|
|
||||||
from .requestargs import RequestArgument, RequestArguments
|
from .requestargs import RequestArgument, RequestArguments
|
||||||
from .responses import PageletResponse, RedirectResponse, TemplateResponse
|
from .responses import PageletResponse, RedirectResponse, TemplateResponse
|
||||||
from .httpd import MatematWebserver, HttpHandler, pagelet
|
from .httpd import MatematWebserver, HttpHandler, pagelet, pagelet_init
|
||||||
from .config import parse_config_file
|
from .config import parse_config_file
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
from typing import Any, Callable, Dict, Tuple, Type, Union
|
from typing import Any, Callable, Dict, Set, Tuple, Type, Union
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -38,6 +38,8 @@ _PAGELET_PATHS: Dict[str, Callable[[str, # HTTP method (GET, POST, ...)
|
||||||
bytes, str, # Response body: will assign HTTP/1.0 200 OK
|
bytes, str, # Response body: will assign HTTP/1.0 200 OK
|
||||||
PageletResponse, # A generic response
|
PageletResponse, # A generic response
|
||||||
]]] = dict()
|
]]] = dict()
|
||||||
|
# The pagelet initialization functions, to be executed upon startup
|
||||||
|
_PAGELET_INIT_FUNCTIONS: Set[Callable[[Dict[str, str], logging.Logger], None]] = set()
|
||||||
|
|
||||||
# Inactivity timeout for client sessions
|
# Inactivity timeout for client sessions
|
||||||
_SESSION_TIMEOUT: int = 3600
|
_SESSION_TIMEOUT: int = 3600
|
||||||
|
@ -94,6 +96,28 @@ def pagelet(path: str):
|
||||||
return http_handler
|
return http_handler
|
||||||
|
|
||||||
|
|
||||||
|
def pagelet_init(fun: Callable[[Dict[str, str], logging.Logger], None]):
|
||||||
|
"""
|
||||||
|
Annotate a function to act as a pagelet initialization function. The function will be called when the webserver is
|
||||||
|
started. The function should set up everything required for pagelets to operate correctly, e.g. providing default
|
||||||
|
values for configuration items, or performing database schema migrations.
|
||||||
|
|
||||||
|
Any exception thrown by an initialization function will cause the web server to seize operation. Multiple
|
||||||
|
initialization functions can be used.
|
||||||
|
|
||||||
|
The function must have the following signature:
|
||||||
|
|
||||||
|
(config: Dict[str, str], logger: logging.Logger) -> None
|
||||||
|
|
||||||
|
config: The mutable dictionary of variables read from the [Pagelets] section of the configuration file.
|
||||||
|
logger: The server's logger instance.
|
||||||
|
returns: Nothing.
|
||||||
|
|
||||||
|
:param fun: The function to annotate
|
||||||
|
"""
|
||||||
|
_PAGELET_INIT_FUNCTIONS.add(fun)
|
||||||
|
|
||||||
|
|
||||||
class MatematHTTPServer(HTTPServer):
|
class MatematHTTPServer(HTTPServer):
|
||||||
"""
|
"""
|
||||||
A http.server.HTTPServer subclass that acts as a container for data that must be persistent between requests.
|
A http.server.HTTPServer subclass that acts as a container for data that must be persistent between requests.
|
||||||
|
@ -184,8 +208,20 @@ class MatematWebserver(object):
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start the web server. This call blocks while the server is running.
|
Call all pagelet initialization functions and start the web server. This call blocks while the server is
|
||||||
|
running. If any exception is raised in the initialization phase, the program is terminated with a non-zero
|
||||||
|
exit code.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
# Run all pagelet initialization functions
|
||||||
|
for fun in _PAGELET_INIT_FUNCTIONS:
|
||||||
|
fun(self._httpd.pagelet_variables, self._httpd.logger)
|
||||||
|
except BaseException as e:
|
||||||
|
# If an error occurs, log it and terminate
|
||||||
|
self._httpd.logger.exception(e)
|
||||||
|
self._httpd.logger.critical('An initialization pagelet raised an error. Stopping.')
|
||||||
|
raise e
|
||||||
|
# If pagelet initialization went fine, start the HTTP server
|
||||||
self._httpd.serve_forever()
|
self._httpd.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ This package contains the pagelet functions served by the Matemat software.
|
||||||
A new pagelet function must be imported here to be automatically loaded when the server is started.
|
A new pagelet function must be imported here to be automatically loaded when the server is started.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .initialization import initialization
|
||||||
from .main import main_page
|
from .main import main_page
|
||||||
from .login import login_page
|
from .login import login_page
|
||||||
from .logout import logout
|
from .logout import logout
|
||||||
|
|
28
matemat/webserver/pagelets/initialization.py
Normal file
28
matemat/webserver/pagelets/initialization.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from matemat.webserver import pagelet_init
|
||||||
|
from matemat.db import MatematDatabase
|
||||||
|
|
||||||
|
|
||||||
|
@pagelet_init
|
||||||
|
def initialization(config: Dict[str, str],
|
||||||
|
logger: logging.Logger) -> None:
|
||||||
|
"""
|
||||||
|
The pagelet initialization function. Makes sure everything is ready for operation. If anything fails here, the web
|
||||||
|
server won't resume operation.
|
||||||
|
"""
|
||||||
|
# Set default values for missing config items
|
||||||
|
if 'InstanceName' not in config:
|
||||||
|
config['InstanceName'] = 'Matemat'
|
||||||
|
logger.warning('Property \'InstanceName\' not set, using \'Matemat\'')
|
||||||
|
if 'UploadDir' not in config:
|
||||||
|
config['UploadDir'] = './static/upload/'
|
||||||
|
logger.warning('Property \'UploadDir\' not set, using \'./static/upload/\'')
|
||||||
|
if 'DatabaseFile' not in config:
|
||||||
|
config['DatabaseFile'] = './matemat.db'
|
||||||
|
logger.warning('Property \'DatabaseFile\' not set, using \'./matemat.db\'')
|
||||||
|
with MatematDatabase(config['DatabaseFile']):
|
||||||
|
# Connect to the database to create it and perform any schema migrations
|
||||||
|
pass
|
68
matemat/webserver/test/test_pagelet_init.py
Normal file
68
matemat/webserver/test/test_pagelet_init.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import http.client
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from matemat.webserver import MatematWebserver, RequestArguments, pagelet_init, pagelet
|
||||||
|
|
||||||
|
|
||||||
|
@pagelet('/just/testing/init')
|
||||||
|
def init_test_pagelet(method: str,
|
||||||
|
path: str,
|
||||||
|
args: RequestArguments,
|
||||||
|
session_vars: Dict[str, Any],
|
||||||
|
headers: Dict[str, str],
|
||||||
|
pagelet_variables: Dict[str, str]):
|
||||||
|
return pagelet_variables['Unit-Test']
|
||||||
|
|
||||||
|
|
||||||
|
_INIT_FAIL = False
|
||||||
|
|
||||||
|
|
||||||
|
@pagelet_init
|
||||||
|
def init(config: Dict[str, str],
|
||||||
|
logger: logging.Logger):
|
||||||
|
if _INIT_FAIL:
|
||||||
|
raise ValueError('This error should be raised!')
|
||||||
|
config['Unit-Test'] = 'Pagelet Init Test'
|
||||||
|
|
||||||
|
|
||||||
|
class TestPageletInitialization(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.srv = MatematWebserver('::1', 0, '/nonexistent', '/nonexistent', {}, {},
|
||||||
|
logging.NOTSET, logging.NullHandler())
|
||||||
|
self.srv_port = int(self.srv._httpd.socket.getsockname()[1])
|
||||||
|
self.timer = threading.Timer(5.0, self.srv._httpd.shutdown)
|
||||||
|
self.timer.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.timer.cancel()
|
||||||
|
if self.srv is not None:
|
||||||
|
self.srv._httpd.socket.close()
|
||||||
|
global _INIT_FAIL
|
||||||
|
_INIT_FAIL = False
|
||||||
|
|
||||||
|
def test_pagelet_init_ok(self):
|
||||||
|
"""
|
||||||
|
Test successful pagelet initialization
|
||||||
|
"""
|
||||||
|
thread = threading.Thread(target=self.srv.start)
|
||||||
|
thread.start()
|
||||||
|
con = http.client.HTTPConnection(f'[::1]:{self.srv_port}')
|
||||||
|
con.request('GET', '/just/testing/init')
|
||||||
|
response = con.getresponse().read()
|
||||||
|
self.srv._httpd.shutdown()
|
||||||
|
self.assertEqual(b'Pagelet Init Test', response)
|
||||||
|
|
||||||
|
def test_pagelet_init_fail(self):
|
||||||
|
"""
|
||||||
|
Test unsuccessful pagelet initialization
|
||||||
|
"""
|
||||||
|
global _INIT_FAIL
|
||||||
|
_INIT_FAIL = True
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.srv.start()
|
19
setup.cfg
19
setup.cfg
|
@ -1,3 +1,22 @@
|
||||||
|
|
||||||
|
#
|
||||||
|
# PyCodestyle
|
||||||
|
#
|
||||||
|
|
||||||
[pycodestyle]
|
[pycodestyle]
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
statistics = True
|
statistics = True
|
||||||
|
|
||||||
|
#
|
||||||
|
# Coverage
|
||||||
|
#
|
||||||
|
|
||||||
|
[run]
|
||||||
|
branch = True
|
||||||
|
parallel = True
|
||||||
|
source = matemat/
|
||||||
|
|
||||||
|
[report]
|
||||||
|
show_missing = True
|
||||||
|
include = matemat/*
|
||||||
|
omit = */test/*.py
|
||||||
|
|
Loading…
Reference in a new issue