Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9abd5b72f4 | ||
|
|
2e953ea098 | ||
|
|
211ae1e2d9 | ||
|
|
84ea46ae76 | ||
|
|
73f6c6635b | ||
|
|
babb14288c | ||
|
|
3bb97a6dcc | ||
|
|
9e6cd57532 | ||
|
|
28c16e1127 | ||
|
|
4815b97200 | ||
|
|
9f6418bf60 | ||
|
|
780d6a9b3c | ||
|
|
32e3c2860d | ||
|
|
029f4a9eb2 | ||
|
|
a6d9b50b93 | ||
|
|
9723f98950 | ||
|
|
1a93dad562 | ||
|
|
44df95a434 | ||
|
|
fd56f8ca63 | ||
|
|
77fabf28f6 | ||
|
|
0038663bc6 | ||
|
|
6489e4452e | ||
|
|
bfd1efa9f9 | ||
|
|
293d7972f1 | ||
|
|
d30226d55b | ||
|
|
9e454a83d0 | ||
|
|
34cadcf9b1 | ||
|
|
a9d6794c4d | ||
|
|
1fb13fefff | ||
|
|
d67d2c6bc9 |
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
ignored-modules = zmq
|
ignored-modules = zmq
|
||||||
max-line-length = 150
|
max-line-length = 150
|
||||||
disable = missing-docstring, too-few-public-methods
|
disable = missing-docstring, too-few-public-methods, invalid-name
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ COPY nginx/default.conf ${TFW_NGINX_DEFAULT}
|
|||||||
COPY nginx/components/ ${TFW_NGINX_COMPONENTS}
|
COPY nginx/components/ ${TFW_NGINX_COMPONENTS}
|
||||||
COPY lib LICENSE ${TFW_LIB_DIR}
|
COPY lib LICENSE ${TFW_LIB_DIR}
|
||||||
|
|
||||||
RUN for dir in "${TFW_LIB_DIR}"/{tfw,tao,envvars.py} "/etc/nginx" "/etc/supervisor"; do \
|
RUN for dir in "${TFW_LIB_DIR}"/{tfw,tao,envvars} "/etc/nginx" "/etc/supervisor"; do \
|
||||||
chown -R root:root "$dir" && chmod -R 700 "$dir"; \
|
chown -R root:root "$dir" && chmod -R 700 "$dir"; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ from .terminal_event_handler import TerminalEventHandler
|
|||||||
from .ide_event_handler import IdeEventHandler
|
from .ide_event_handler import IdeEventHandler
|
||||||
from .history_monitor import HistoryMonitor, BashMonitor, GDBMonitor
|
from .history_monitor import HistoryMonitor, BashMonitor, GDBMonitor
|
||||||
from .terminal_commands import TerminalCommands
|
from .terminal_commands import TerminalCommands
|
||||||
|
from .log_monitoring_event_handler import LogMonitoringEventHandler
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DirectoryMonitor(ObserverMixin):
|
class DirectoryMonitor(ObserverMixin):
|
||||||
def __init__(self, directory):
|
def __init__(self, directories):
|
||||||
ObserverMixin.__init__(self)
|
ObserverMixin.__init__(self)
|
||||||
self.eventhandler = IdeReloadWatchdogEventHandler()
|
self.eventhandler = IdeReloadWatchdogEventHandler()
|
||||||
self.observer.schedule(self.eventhandler, directory, recursive=True)
|
for directory in directories:
|
||||||
|
self.observer.schedule(self.eventhandler, directory, recursive=True)
|
||||||
|
|
||||||
self.pause, self.resume = self.eventhandler.pause, self.eventhandler.resume
|
self.pause, self.resume = self.eventhandler.pause, self.eventhandler.resume
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -98,17 +98,24 @@ class IdeEventHandler(EventHandlerBase, MonitorManagerMixin):
|
|||||||
By default all files in the directory specified in __init__ are displayed
|
By default all files in the directory specified in __init__ are displayed
|
||||||
on the fontend. Note that this is a stateful component.
|
on the fontend. Note that this is a stateful component.
|
||||||
|
|
||||||
|
When any file in the selected directory changes they are automatically refreshed
|
||||||
|
on the frontend (this is done by listening to inotify events).
|
||||||
|
|
||||||
This EventHandler accepts messages that have a data["command"] key specifying
|
This EventHandler accepts messages that have a data["command"] key specifying
|
||||||
a command to be executed.
|
a command to be executed.
|
||||||
The API of each command is documented in their respective handlers.
|
The API of each command is documented in their respective handlers.
|
||||||
"""
|
"""
|
||||||
def __init__(self, key, directory, allowed_directories, selected_file=None, exclude=None):
|
def __init__(self, key, directory, allowed_directories, selected_file=None, exclude=None,
|
||||||
|
additional_watched_directories=None):
|
||||||
"""
|
"""
|
||||||
:param key: the key this instance should listen to
|
:param key: the key this instance should listen to
|
||||||
:param directory: working directory which the EventHandler should serve files from
|
:param directory: working directory which the EventHandler should serve files from
|
||||||
:param allowed_directories: list of directories that can be switched to using the selectdir command
|
:param allowed_directories: list of directories that can be switched to using the selectdir command
|
||||||
:param selected_file: file that is selected by default
|
:param selected_file: file that is selected by default
|
||||||
:param exclude: list of filenames that should not appear between files (for *.o, *.pyc, etc.)
|
:param exclude: list of filenames that should not appear between files (for *.o, *.pyc, etc.)
|
||||||
|
:param additional_watched_directories: refresh the selected file when files change in these directories
|
||||||
|
(the working directory is watched by default, this is useful for
|
||||||
|
symlinks and such)
|
||||||
"""
|
"""
|
||||||
super().__init__(key)
|
super().__init__(key)
|
||||||
try:
|
try:
|
||||||
@@ -116,7 +123,11 @@ class IdeEventHandler(EventHandlerBase, MonitorManagerMixin):
|
|||||||
selected_file=selected_file, exclude=exclude)
|
selected_file=selected_file, exclude=exclude)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise EnvironmentError(f'No file(s) in IdeEventHandler working_directory "{directory}"!')
|
raise EnvironmentError(f'No file(s) in IdeEventHandler working_directory "{directory}"!')
|
||||||
MonitorManagerMixin.__init__(self, DirectoryMonitor, self.filemanager.workdir)
|
|
||||||
|
self.watched_directories = [self.filemanager.workdir]
|
||||||
|
if additional_watched_directories:
|
||||||
|
self.watched_directories.extend(additional_watched_directories)
|
||||||
|
MonitorManagerMixin.__init__(self, DirectoryMonitor, self.watched_directories)
|
||||||
|
|
||||||
self.commands = {'read': self.read,
|
self.commands = {'read': self.read,
|
||||||
'write': self.write,
|
'write': self.write,
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
||||||
|
# All Rights Reserved. See LICENSE file for details.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from os.path import dirname
|
||||||
|
|
||||||
|
from watchdog.events import PatternMatchingEventHandler as PatternMatchingWatchdogEventHandler
|
||||||
|
|
||||||
|
from tfw.networking.event_handlers import ServerUplinkConnector
|
||||||
|
from tfw.decorators import RateLimiter
|
||||||
|
from tfw.mixins import ObserverMixin, SupervisorLogMixin
|
||||||
|
|
||||||
|
|
||||||
|
class LogMonitor(ObserverMixin):
|
||||||
|
def __init__(self, process_name, log_tail=0):
|
||||||
|
self.prevent_log_recursion()
|
||||||
|
ObserverMixin.__init__(self)
|
||||||
|
event_handler = SendLogWatchdogEventHandler(process_name, log_tail=log_tail)
|
||||||
|
self.observer.schedule(
|
||||||
|
event_handler,
|
||||||
|
event_handler.path
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def prevent_log_recursion():
|
||||||
|
# This is done to prevent inotify event logs triggering themselves (infinite log recursion)
|
||||||
|
logging.getLogger('watchdog.observers.inotify_buffer').propagate = False
|
||||||
|
|
||||||
|
|
||||||
|
class SendLogWatchdogEventHandler(PatternMatchingWatchdogEventHandler, SupervisorLogMixin):
|
||||||
|
def __init__(self, process_name, log_tail=0):
|
||||||
|
self.acquire_own_supervisor_instance() # This thread-localises the xmlrpc client
|
||||||
|
self.process_name = process_name
|
||||||
|
self.procinfo = self.supervisor.getProcessInfo(self.process_name)
|
||||||
|
super().__init__([self.procinfo['stdout_logfile'], self.procinfo['stderr_logfile']])
|
||||||
|
self.uplink = ServerUplinkConnector()
|
||||||
|
self.log_tail = log_tail
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return dirname(self.procinfo['stdout_logfile'])
|
||||||
|
|
||||||
|
@RateLimiter(rate_per_second=5)
|
||||||
|
def on_modified(self, event):
|
||||||
|
self.uplink.send({
|
||||||
|
'key': 'processlog',
|
||||||
|
'data': {
|
||||||
|
'command': 'new_log',
|
||||||
|
'stdout': self.read_stdout(self.process_name, tail=self.log_tail),
|
||||||
|
'stderr': self.read_stderr(self.process_name, tail=self.log_tail)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
||||||
|
# All Rights Reserved. See LICENSE file for details.
|
||||||
|
|
||||||
|
|
||||||
|
from tfw import EventHandlerBase
|
||||||
|
from tfw.mixins import MonitorManagerMixin
|
||||||
|
from tfw.config.logs import logging
|
||||||
|
from .log_monitor import LogMonitor
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class LogMonitoringEventHandler(EventHandlerBase, MonitorManagerMixin):
|
||||||
|
def __init__(self, key, process_name, log_tail=0):
|
||||||
|
super().__init__(key)
|
||||||
|
self.process_name = process_name
|
||||||
|
self.log_tail = log_tail
|
||||||
|
MonitorManagerMixin.__init__(self, LogMonitor, self.process_name, self.log_tail)
|
||||||
|
|
||||||
|
self.command_handlers = {
|
||||||
|
'process_name': self.handle_process_name,
|
||||||
|
'log_tail': self.handle_log_tail
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle_event(self, message):
|
||||||
|
try:
|
||||||
|
data = message['data']
|
||||||
|
self.command_handlers[data['command']](data)
|
||||||
|
self.reload_monitor()
|
||||||
|
except KeyError:
|
||||||
|
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
|
||||||
|
|
||||||
|
def handle_process_name(self, data):
|
||||||
|
self.set_monitor_args(data['process_name'], self.log_tail)
|
||||||
|
|
||||||
|
def handle_log_tail(self, data):
|
||||||
|
self.set_monitor_args(self.process_name, data['log_tail'])
|
||||||
@@ -4,19 +4,18 @@
|
|||||||
from xmlrpc.client import Fault as SupervisorFault
|
from xmlrpc.client import Fault as SupervisorFault
|
||||||
|
|
||||||
from tfw import EventHandlerBase
|
from tfw import EventHandlerBase
|
||||||
from tfw.mixins import SupervisorMixin
|
from tfw.mixins import SupervisorMixin, SupervisorLogMixin
|
||||||
from tfw.config.logs import logging
|
from tfw.config.logs import logging
|
||||||
from .directory_monitor import with_monitor_paused
|
from .directory_monitor import with_monitor_paused
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ProcessManager(SupervisorMixin):
|
class ProcessManager(SupervisorMixin, SupervisorLogMixin):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.commands = {'start': self.start_process,
|
self.commands = {'start': self.start_process,
|
||||||
'stop': self.stop_process,
|
'stop': self.stop_process,
|
||||||
'restart': self.restart_process,
|
'restart': self.restart_process}
|
||||||
'readlog': self.read_log}
|
|
||||||
|
|
||||||
def __call__(self, command, process_name):
|
def __call__(self, command, process_name):
|
||||||
return self.commands[command](process_name)
|
return self.commands[command](process_name)
|
||||||
@@ -35,21 +34,24 @@ class ProcessManagingEventHandler(EventHandlerBase):
|
|||||||
Commands available: start, stop, restart, readlog
|
Commands available: start, stop, restart, readlog
|
||||||
(the names are as self-documenting as it gets)
|
(the names are as self-documenting as it gets)
|
||||||
"""
|
"""
|
||||||
def __init__(self, key, dirmonitor=None):
|
def __init__(self, key, dirmonitor=None, log_tail=0):
|
||||||
super().__init__(key)
|
super().__init__(key)
|
||||||
self.key = key
|
self.key = key
|
||||||
self.monitor = dirmonitor
|
self.monitor = dirmonitor
|
||||||
self.processmanager = ProcessManager()
|
self.processmanager = ProcessManager()
|
||||||
|
self.log_tail = log_tail
|
||||||
|
|
||||||
@with_monitor_paused
|
@with_monitor_paused
|
||||||
def handle_event(self, message):
|
def handle_event(self, message):
|
||||||
try:
|
try:
|
||||||
data = message['data']
|
data = message['data']
|
||||||
self.processmanager(data['command'], data['process_name'])
|
try:
|
||||||
|
self.processmanager(data['command'], data['process_name'])
|
||||||
|
except SupervisorFault as fault:
|
||||||
|
message['data']['error'] = fault.faultString
|
||||||
|
finally:
|
||||||
|
message['data']['stdout'] = self.processmanager.read_stdout(data['process_name'], self.log_tail)
|
||||||
|
message['data']['stderr'] = self.processmanager.read_stderr(data['process_name'], self.log_tail)
|
||||||
return message
|
return message
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
|
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
|
||||||
except SupervisorFault as fault:
|
|
||||||
message['data']['error'] = fault.faultString
|
|
||||||
message['data']['log'] = self.processmanager.read_log(message['data']['process_name'])
|
|
||||||
return message
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from re import match
|
from re import match
|
||||||
|
from shlex import split
|
||||||
|
|
||||||
from tfw.config.logs import logging
|
from tfw.config.logs import logging
|
||||||
|
|
||||||
@@ -57,10 +58,10 @@ class TerminalCommands(ABC):
|
|||||||
return match(self._command_method_regex, string)
|
return match(self._command_method_regex, string)
|
||||||
|
|
||||||
def callback(self, history):
|
def callback(self, history):
|
||||||
parts = history[-1].split()
|
parts = split(history[-1])
|
||||||
command = parts[0]
|
command = parts[0]
|
||||||
if command in self.command_implemetations.keys():
|
if command in self.command_implemetations.keys():
|
||||||
try:
|
try:
|
||||||
self.command_implemetations[command](*parts[1:])
|
self.command_implemetations[command](*parts[1:])
|
||||||
except IndexError:
|
except Exception: # pylint: disable=broad-except
|
||||||
LOG.debug('Command "%s" failed!', command)
|
LOG.exception('Command "%s" failed:', command)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
||||||
# All Rights Reserved. See LICENSE file for details.
|
# All Rights Reserved. See LICENSE file for details.
|
||||||
|
|
||||||
from .supervisor_mixin import SupervisorMixin
|
from .supervisor_mixin import SupervisorMixin, SupervisorLogMixin
|
||||||
from .callback_mixin import CallbackMixin
|
from .callback_mixin import CallbackMixin
|
||||||
from .observer_mixin import ObserverMixin
|
from .observer_mixin import ObserverMixin
|
||||||
from .monitor_manager_mixin import MonitorManagerMixin
|
from .monitor_manager_mixin import MonitorManagerMixin
|
||||||
|
|||||||
@@ -7,21 +7,24 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class MonitorManagerMixin:
|
class MonitorManagerMixin:
|
||||||
def __init__(self, monitor_type, directory):
|
def __init__(self, monitor_type, *monitor_args):
|
||||||
self._monitor_type = monitor_type
|
self._monitor_type = monitor_type
|
||||||
self._monitor = None
|
self._monitor = None
|
||||||
self._monitored_directory = directory
|
self.monitor_args = monitor_args
|
||||||
self.reload_monitor()
|
self.reload_monitor()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def monitor(self):
|
def monitor(self):
|
||||||
return self._monitor
|
return self._monitor
|
||||||
|
|
||||||
|
def set_monitor_args(self, *monitor_args):
|
||||||
|
self.monitor_args = monitor_args
|
||||||
|
|
||||||
def reload_monitor(self):
|
def reload_monitor(self):
|
||||||
if self._monitor:
|
if self._monitor:
|
||||||
try:
|
try:
|
||||||
self._monitor.stop()
|
self._monitor.stop()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.debug('Working directory was removed – ignoring...')
|
LOG.debug('Working directory was removed – ignoring...')
|
||||||
self._monitor = self._monitor_type(self._monitored_directory)
|
self._monitor = self._monitor_type(*self.monitor_args)
|
||||||
self._monitor.watch() # This runs on a separate thread
|
self._monitor.watch() # This runs on a separate thread
|
||||||
|
|||||||
@@ -9,9 +9,21 @@ from os import remove
|
|||||||
from tfw.config import TFWENV
|
from tfw.config import TFWENV
|
||||||
|
|
||||||
|
|
||||||
class SupervisorMixin:
|
def get_supervisor_instance():
|
||||||
supervisor = xmlrpc.client.ServerProxy(TFWENV.SUPERVISOR_HTTP_URI).supervisor
|
return xmlrpc.client.ServerProxy(TFWENV.SUPERVISOR_HTTP_URI).supervisor
|
||||||
|
|
||||||
|
|
||||||
|
class SupervisorBaseMixin:
|
||||||
|
supervisor = get_supervisor_instance()
|
||||||
|
|
||||||
|
def acquire_own_supervisor_instance(self):
|
||||||
|
"""
|
||||||
|
Give this instance non-static, local xmlrpc client
|
||||||
|
"""
|
||||||
|
self.supervisor = get_supervisor_instance()
|
||||||
|
|
||||||
|
|
||||||
|
class SupervisorMixin(SupervisorBaseMixin):
|
||||||
def stop_process(self, process_name):
|
def stop_process(self, process_name):
|
||||||
with suppress(SupervisorFault):
|
with suppress(SupervisorFault):
|
||||||
self.supervisor.stopProcess(process_name)
|
self.supervisor.stopProcess(process_name)
|
||||||
@@ -19,12 +31,20 @@ class SupervisorMixin:
|
|||||||
def start_process(self, process_name):
|
def start_process(self, process_name):
|
||||||
self.supervisor.startProcess(process_name)
|
self.supervisor.startProcess(process_name)
|
||||||
|
|
||||||
def read_log(self, process_name):
|
|
||||||
logs = self.supervisor.readProcessStderrLog(process_name, 0, 0)
|
|
||||||
remove(self.supervisor.getProcessInfo(process_name)['stderr_logfile'])
|
|
||||||
self.supervisor.clearProcessLogs(process_name)
|
|
||||||
return logs
|
|
||||||
|
|
||||||
def restart_process(self, process_name):
|
def restart_process(self, process_name):
|
||||||
self.stop_process(process_name)
|
self.stop_process(process_name)
|
||||||
self.start_process(process_name)
|
self.start_process(process_name)
|
||||||
|
|
||||||
|
|
||||||
|
class SupervisorLogMixin(SupervisorBaseMixin):
|
||||||
|
def read_stdout(self, process_name, tail=0):
|
||||||
|
return self.supervisor.readProcessStdoutLog(process_name, -tail, 0)
|
||||||
|
|
||||||
|
def read_stderr(self, process_name, tail=0):
|
||||||
|
return self.supervisor.readProcessStderrLog(process_name, -tail, 0)
|
||||||
|
|
||||||
|
def clear_logs(self, process_name):
|
||||||
|
for logfile in ('stdout_logfile', 'stderr_logfile'):
|
||||||
|
with suppress(FileNotFoundError):
|
||||||
|
remove(self.supervisor.getProcessInfo(process_name)[logfile])
|
||||||
|
self.supervisor.clearProcessLogs(process_name)
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ class ServerUplinkConnector(ZMQConnectorBase):
|
|||||||
def send_to_eventhandler(self, message):
|
def send_to_eventhandler(self, message):
|
||||||
"""
|
"""
|
||||||
Send a message to an event handler.
|
Send a message to an event handler.
|
||||||
|
This envelopes the desired message in the 'data' field of the message to
|
||||||
|
TFWServer, which will mirror it to event handlers.
|
||||||
|
|
||||||
:param message: JSON message you want to send
|
:param message: JSON message you want to send
|
||||||
:param message['key']: key of event handler you want to address
|
:param message['key']: key of event handler you want to address
|
||||||
"""
|
"""
|
||||||
@@ -46,6 +49,7 @@ class ServerUplinkConnector(ZMQConnectorBase):
|
|||||||
def send(self, message):
|
def send(self, message):
|
||||||
"""
|
"""
|
||||||
Send a message to the TFW server
|
Send a message to the TFW server
|
||||||
|
|
||||||
:param message: JSON message you want to send
|
:param message: JSON message you want to send
|
||||||
"""
|
"""
|
||||||
self._zmq_push_socket.send_multipart(serialize_tfw_msg(message))
|
self._zmq_push_socket.send_multipart(serialize_tfw_msg(message))
|
||||||
|
|||||||
@@ -29,10 +29,16 @@ def validate_message(message):
|
|||||||
|
|
||||||
|
|
||||||
def serialize_tfw_msg(message):
|
def serialize_tfw_msg(message):
|
||||||
|
"""
|
||||||
|
Create TFW multipart data from message dict
|
||||||
|
"""
|
||||||
return _serialize_all(message['key'], message)
|
return _serialize_all(message['key'], message)
|
||||||
|
|
||||||
|
|
||||||
def deserialize_tfw_msg(*args):
|
def deserialize_tfw_msg(*args):
|
||||||
|
"""
|
||||||
|
Return message from TFW multipart data
|
||||||
|
"""
|
||||||
return _deserialize_all(*args)[1]
|
return _deserialize_all(*args)[1]
|
||||||
|
|
||||||
|
|
||||||
@@ -45,12 +51,20 @@ def _deserialize_all(*args):
|
|||||||
|
|
||||||
|
|
||||||
def _serialize_single(data):
|
def _serialize_single(data):
|
||||||
|
"""
|
||||||
|
Return input as bytes
|
||||||
|
(serialize input if it is JSON)
|
||||||
|
"""
|
||||||
if not isinstance(data, str):
|
if not isinstance(data, str):
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
return _encode_if_needed(data)
|
return _encode_if_needed(data)
|
||||||
|
|
||||||
|
|
||||||
def _deserialize_single(data):
|
def _deserialize_single(data):
|
||||||
|
"""
|
||||||
|
Try parsing input as JSON, return it as
|
||||||
|
string if parsing fails.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return json.loads(data)
|
return json.loads(data)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -58,12 +72,20 @@ def _deserialize_single(data):
|
|||||||
|
|
||||||
|
|
||||||
def _encode_if_needed(value):
|
def _encode_if_needed(value):
|
||||||
|
"""
|
||||||
|
Return input as bytes
|
||||||
|
(encode if input is string)
|
||||||
|
"""
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = value.encode('utf-8')
|
value = value.encode('utf-8')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _decode_if_needed(value):
|
def _decode_if_needed(value):
|
||||||
|
"""
|
||||||
|
Return input as string
|
||||||
|
(decode if input is bytes)
|
||||||
|
"""
|
||||||
if isinstance(value, (bytes, bytearray)):
|
if isinstance(value, (bytes, bytearray)):
|
||||||
value = value.decode('utf-8')
|
value = value.decode('utf-8')
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ server {
|
|||||||
proxy_connect_timeout 7d;
|
proxy_connect_timeout 7d;
|
||||||
proxy_send_timeout 7d;
|
proxy_send_timeout 7d;
|
||||||
proxy_read_timeout 7d;
|
proxy_read_timeout 7d;
|
||||||
|
absolute_redirect off;
|
||||||
|
|
||||||
location = /ws {
|
location = /ws {
|
||||||
proxy_pass http://127.0.0.1:${TFW_WEB_PORT};
|
proxy_pass http://127.0.0.1:${TFW_WEB_PORT};
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from os.path import dirname, realpath, join
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
|
here = dirname(realpath(__file__))
|
||||||
|
|
||||||
|
with open(join(here, 'VERSION'), 'r') as ifile:
|
||||||
|
version = ifile.read().strip('\n')
|
||||||
|
with open(join(here, 'requirements.txt'), 'r') as ifile:
|
||||||
|
requirements = ifile.read().splitlines()
|
||||||
|
|
||||||
|
setup(name='tfw',
|
||||||
|
version=version,
|
||||||
|
description='Avatao tutorial-framework',
|
||||||
|
url='https://github.com/avatao-content/baseimage-tutorial-framework',
|
||||||
|
author='Avatao.com Innovative Learning Kft.',
|
||||||
|
author_email='support@avatao.com',
|
||||||
|
license='custom',
|
||||||
|
packages=find_packages('lib'),
|
||||||
|
package_dir={'': 'lib'},
|
||||||
|
install_requires=requirements,
|
||||||
|
zip_safe=False)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
[supervisord]
|
[supervisord]
|
||||||
user=root
|
user=root
|
||||||
logfile = /tmp/supervisord.log
|
logfile=/dev/null
|
||||||
|
logfile_maxbytes=0
|
||||||
loglevel = debug
|
loglevel = debug
|
||||||
pidfile = /tmp/supervisord.pid
|
pidfile = /tmp/supervisord.pid
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user