Browse Source

backend: Create Python package 'monsun_backend'

Andreas Berthoud 4 years ago
parent
commit
15ccad6106
  1. 1
      .gitignore
  2. 4
      backend/.gitignore
  3. 58
      backend/container.py
  4. 22
      backend/monsun_backend/__init__.py
  5. 0
      backend/monsun_backend/command_endpoint.py
  6. 14
      backend/monsun_backend/command_execution.py
  7. 0
      backend/monsun_backend/commands.py
  8. 56
      backend/monsun_backend/container.py
  9. 2
      backend/monsun_backend/defaults/config.yml
  10. 27
      backend/monsun_backend/util.py
  11. 4
      backend/requirements.txt
  12. 3
      backend/runsever.py
  13. 19
      backend/setup.py
  14. 3
      backend/wsgi.py
  15. 1
      config_example.yml
  16. 3
      requirements.txt

1
.gitignore

@ -65,3 +65,4 @@ Release/
.vscode/ .vscode/
venv*/ venv*/
*.pyc *.pyc
config.yml

4
backend/.gitignore

@ -0,0 +1,4 @@
**/__pycache__/
build/
dist/
**/*.egg-info

58
backend/container.py

@ -1,58 +0,0 @@
from pathlib import Path
from typing import Dict
from typing import Optional
from dependency_injector import containers
from dependency_injector import providers
from flask_api import FlaskAPI
from serial import Serial
DEFAULTS_DIR = Path(__file__).parent / "defaults"
CONFIG_FILE = DEFAULTS_DIR / "config.yml"
class Container(containers.DeclarativeContainer):
config = providers.Configuration("config")
serial = providers.Factory(
Serial,
port=config.device_id.required(),
baudrate=config.baudrate.required(),
)
__container: Optional[Container] = None
def initialize_container(
config: Optional[Dict] = None,
config_file: Optional[Path] = None,
) -> Container:
global __container
if __container is not None:
raise RuntimeError("Container already initialized")
__container = Container()
__container.config.from_yaml(CONFIG_FILE)
if config is not None:
__container.config.from_dict(config)
if config_file is not None:
__container.config.from_yaml(config_file)
return __container
def init_app(app: FlaskAPI):
initialize_container(
config=app.config.get("DI_CONFIG"),
config_file=app.config.get("DI_CONFIG_FILE"),
)
def get_container() -> Container:
global __container
if __container is None:
raise RuntimeError("Container not initialized")
return __container

22
backend/__init__.py → backend/monsun_backend/__init__.py

@ -5,7 +5,12 @@ from flask_api import FlaskAPI
from . import command_endpoint from . import command_endpoint
from . import command_execution from . import command_execution
from . import container
__title__ = "monsun-backend"
__author__ = "Andreas Berthoud, Fabian Klein"
__version__ = "0.0.0"
__email__ = "andreasberthoud@gmail.com"
__copyright__ = f"2021 {__author__}"
def create_app() -> FlaskAPI: def create_app() -> FlaskAPI:
@ -15,23 +20,30 @@ def create_app() -> FlaskAPI:
dictConfig( dictConfig(
{ {
"version": 1, "version": 1,
"disable_existing_loggers": False,
"formatters": { "formatters": {
"default": { "default": {
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s", "format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
}, },
}, },
"handlers": { "handlers": {
"wsgi": { "console": {
"class": "logging.StreamHandler", "class": "logging.StreamHandler",
"stream": "ext://flask.logging.wsgi_errors_stream",
"formatter": "default", "formatter": "default",
"stream": "ext://sys.stdout",
},
},
"root": {"level": "NOTSET", "handlers": ["console"]},
"logger": {
"monsun_backend": {
"level": "DEBUG",
"propagate": "no",
}, },
}, },
"root": {"level": "DEBUG", "handlers": ["wsgi"]},
}, },
) )
container.init_app(app) # container.init_app(app)
if os.environ.get("WERKZEUG_RUN_MAIN") != "true": if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
# prevent from be called twice in debug mode # prevent from be called twice in debug mode

0
backend/command_endpoint.py → backend/monsun_backend/command_endpoint.py

14
backend/command_execution.py → backend/monsun_backend/command_execution.py

@ -17,8 +17,7 @@ from .commands import Command
from .commands import CommandId from .commands import CommandId
from .commands import Request from .commands import Request
from .commands import Response from .commands import Response
from .container import get_container from .container import get_initialize_container
from .container import initialize_container
_logger = logging.getLogger(__file__) _logger = logging.getLogger(__file__)
@ -34,7 +33,6 @@ class State(Enum):
def worker_process( def worker_process(
queue: Queue, queue: Queue,
config,
): ):
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG, level=logging.DEBUG,
@ -43,8 +41,7 @@ def worker_process(
logger = logging.getLogger("Command Loop") logger = logging.getLogger("Command Loop")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
initialize_container(config) container = get_initialize_container()
container = get_container()
heartbeat_interval = container.config.heartbeat_interval() heartbeat_interval = container.config.heartbeat_interval()
serial_reconnection_wait_timeout = ( serial_reconnection_wait_timeout = (
container.config.serial_reconnection_wait_timeout() container.config.serial_reconnection_wait_timeout()
@ -231,15 +228,12 @@ def _end_running_process():
def start_backgroup_process(): def start_backgroup_process():
_logger.warning("start_backgroup_process called") _logger.info("start_backgroup_process called")
global _process global _process
_process = Process( _process = Process(
target=worker_process, target=worker_process,
args=( args=(_command_queue,),
_command_queue,
get_container().config(),
),
) )
_process.start() _process.start()
atexit.register(_end_running_process) atexit.register(_end_running_process)

0
backend/commands.py → backend/monsun_backend/commands.py

56
backend/monsun_backend/container.py

@ -0,0 +1,56 @@
import logging
from pathlib import Path
from pprint import pformat
from typing import Dict
from typing import Optional
from dependency_injector import containers
from dependency_injector import providers
from serial import Serial
from .util import log_function_call
DEFAULTS_DIR = Path(__file__).parent / "defaults"
CONFIG_FILE = DEFAULTS_DIR / "config.yml"
_logger = logging.getLogger(__name__)
class Container(containers.DeclarativeContainer):
config = providers.Configuration("config")
serial = providers.Factory(
Serial,
port=config.device_id.required(),
baudrate=config.baudrate.required(),
)
@log_function_call
def get_initialize_container(
config: Optional[Dict] = None,
config_file: Optional[Path] = None,
) -> Container:
logger = _logger.getChild("initialize_container")
logger.debug("initialize container...")
container = Container()
logger.debug(f"initialize container from config file: {CONFIG_FILE}")
container.config.from_yaml(CONFIG_FILE, required=True)
user_config = Path.cwd() / "config.yml"
if user_config.is_file():
logger.debug(f"initialize container from user config file: {user_config}")
container.config.from_yaml(user_config, required=True)
if config is not None:
logger.debug(f"initialize container with config: {config}")
container.config.from_dict(config)
if config_file is not None:
logger.debug(f"initialize container from config file: {config_file}")
container.config.from_yaml(config_file)
logger.debug(f"container config:\n{pformat(container.config())}")
return container

2
backend/defaults/config.yml → backend/monsun_backend/defaults/config.yml

@ -1,5 +1,3 @@
device_id: /dev/tty.usbmodem2067368F32521
# device_id: /dev/tty.usbmodem207E3283544E1
baudrate: 115200 baudrate: 115200
header_size: 4 header_size: 4
heartbeat_interval: 1 heartbeat_interval: 1

27
backend/monsun_backend/util.py

@ -0,0 +1,27 @@
import functools
import logging
function_call_logger = logging.getLogger("call")
# decorator
def log_function_call(func):
if logging.getLogger().level != logging.DEBUG:
return func
@functools.wraps(func)
def wrapper(*args, **kwargs):
function_call_logger.debug(
"calling {func}({arguments})".format(
func=func.__name__,
arguments=", ".join(
[str(value) for value in args]
+ [f"{key}={value}" for key, value in kwargs.items()],
),
),
)
return_val = func(*args, **kwargs)
function_call_logger.debug(f"{func.__name__}() returned {return_val}")
return return_val
return wrapper

4
backend/requirements.txt

@ -0,0 +1,4 @@
dependency-injector[yaml]>=4.34.0,<5
flask-api>=3.0.post1,<4
pyserial>=3.5,<4
uwsgi

3
backend/runsever.py

@ -1,3 +0,0 @@
from . import create_app
application = create_app()

19
backend/setup.py

@ -0,0 +1,19 @@
import setuptools
from monsun_backend import __author__
from monsun_backend import __email__
from monsun_backend import __version__
setuptools.setup(
name="monsun-backend",
version=__version__,
author=__author__,
author_email=__email__,
description="Monsun backend",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
],
python_requires=">=3.7",
include_package_data=True,
)

3
backend/wsgi.py

@ -0,0 +1,3 @@
from monsun_backend import create_app
application = create_app()

1
config_example.yml

@ -0,0 +1 @@
device_id: /dev/tty.usbmodem207E3283544E1

3
requirements.txt

@ -1,4 +1 @@
dependency-injector[yaml]>=4.34.0,<5
flask-api>=3.0.post1,<4
pre-commit pre-commit
pyserial>=3.5,<4

Loading…
Cancel
Save