From 8cf95dac897e0bb49c150a769e2434c7e93643a9 Mon Sep 17 00:00:00 2001 From: ivelin Date: Sat, 28 Aug 2021 20:34:14 +0000 Subject: [PATCH] chore: commit checkpoint --- src/ambianic/server.py | 4 +- .../webapp/{fastapi-app.py => fastapi_app.py} | 46 ++++++------------- .../{fastapi-server.py => fastapi_server.py} | 30 ++++++------ src/ambianic/webapp/server/config_sources.py | 15 +++--- tests/test_rest_api.py | 12 +---- 5 files changed, 41 insertions(+), 66 deletions(-) rename src/ambianic/webapp/{fastapi-app.py => fastapi_app.py} (78%) rename src/ambianic/webapp/{fastapi-server.py => fastapi_server.py} (85%) diff --git a/src/ambianic/server.py b/src/ambianic/server.py index 487217c7..8ed5c379 100644 --- a/src/ambianic/server.py +++ b/src/ambianic/server.py @@ -10,7 +10,7 @@ from ambianic.util import ServiceExit from ambianic import logger, config, get_config_file, \ get_secrets_file, load_config -from ambianic.webapp.flaskr import FlaskServer +from ambianic.webapp.fastapi_server import FastapiServer log = logging.getLogger(__name__) @@ -19,7 +19,7 @@ MAIN_HEARTBEAT_LOG_INTERVAL = 5 ROOT_SERVERS = { 'pipelines': PipelineServer, - 'web': FlaskServer, + 'web': FastapiServer, } class AmbianicServer: diff --git a/src/ambianic/webapp/fastapi-app.py b/src/ambianic/webapp/fastapi_app.py similarity index 78% rename from src/ambianic/webapp/fastapi-app.py rename to src/ambianic/webapp/fastapi_app.py index ccb8f86f..1577baea 100755 --- a/src/ambianic/webapp/fastapi-app.py +++ b/src/ambianic/webapp/fastapi_app.py @@ -6,14 +6,22 @@ from pathlib import Path from requests import get import yaml -from ambianic import config, DEFAULT_DATA_DIR, __version__ +from ambianic import config, __version__ from ambianic.util import ServiceExit, ThreadedJob, ManagedService from ambianic.webapp.server import samples, config_sources from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles app = FastAPI() +def set_data_dir(data_dir: str = None) -> None: + app.data_dir = data_dir + # serve static files from the data directory + data_path = Path(data_dir).resolve() + app.mount("/api/data", StaticFiles(directory=data_path), name="static") + + log = logging.getLogger(__name__) # CORS (Cross-Origin Resource Sharing) Section @@ -69,14 +77,13 @@ def initialize_premium_notification(): return {"status": "OK", "message": "AUTH0_ID SAVED"} -@app.get('/api/timeline', methods=['GET']) -@app.get('/api/timeline.json', methods=['GET']) +@app.get('/api/timeline') +@app.get('/api/timeline.json') def get_timeline(): response_object = {'status': 'success'} req_page = request.args.get('page', default=1, type=int) log.debug('Requested timeline events page" %d', req_page) - nonlocal data_dir - resp = samples.get_timeline(page=req_page, data_dir=data_dir) + resp = samples.get_timeline(page=req_page, data_dir=app.data_dir) response_object['timeline'] = resp log.debug('Returning %d timeline events', len(resp)) # log.debug('Returning samples: %s ', response_object) @@ -161,31 +168,6 @@ def ping(): response_object = 'pong' return jsonify(response_object) -@app.get('/static/') -def get_static_file(path): - return fastapi.send_from_directory('static', path) - -@app.get('/api/data/') -def get_data_file(path): - data_path = Path(DEFAULT_DATA_DIR).resolve() - log.info('Serving static data file from: %r', data_path / path) - return fastapi.send_from_directory(data_path, path) - -@app.get('/client', defaults={'path': 'index.html'}) -@app.get('/client/', defaults={'path': 'index.html'}) -@app.get('/client/') -def get_client_file(path): - if log.level <= logging.DEBUG: # development mode - hostname = fastapi.request.host.split(':')[0] - base_uri = 'http://{host}:1234/'.format(host=hostname) - return get(f'{base_uri}{path}').content - # production mode - return fastapi.send_from_directory('client/dist', path) - -log.debug('Fastapi url map: %s', str(app.url_map)) -log.debug('Fastapi config map: %s', str(app.config)) -log.debug('Fastapi running in %s mode', - 'development' if app.config['DEBUG'] else 'production') - -log.debug('Fastapi app created.') + +log.info('REST API deployed (as a Fastapi app).') diff --git a/src/ambianic/webapp/fastapi-server.py b/src/ambianic/webapp/fastapi_server.py similarity index 85% rename from src/ambianic/webapp/fastapi-server.py rename to src/ambianic/webapp/fastapi_server.py index 12b3fd89..b45579f8 100755 --- a/src/ambianic/webapp/fastapi-server.py +++ b/src/ambianic/webapp/fastapi_server.py @@ -3,6 +3,7 @@ import logging import time import pkg_resources +import uvicorn from pathlib import Path from requests import get import yaml @@ -28,23 +29,15 @@ def __init__(self, config): data_dir = config.get('data_dir', None) if not data_dir: data_dir = DEFAULT_DATA_DIR - self.srv = None - uvi_ip_address = '0.0.0.0' # bind to all local IP addresses - uvi_port = 8778 + self.uvi_ip_address = '0.0.0.0' # bind to all local IP addresses + self.uvi_port = 8778 log.info('starting fastapi/uvicorn web server on %s:%d', ip_address, port) # if Ambianic is in DEBUG mode, start FASTAPI/uvicorn in dev mode if log.level <= logging.DEBUG: - uvi_reload=True - uvi_debug=True - self.srv = uvicorn.run( - app, - host=uvi_ip_address, - port=uvi_port, - reload=uvi_reload, - debug=uvi_debug, - log_level=log.level, - workers=3) - app.data_dir = data_dir + self.uvi_reload=True + self.uvi_debug=True + + app.set_data_dir(data_dir=data_dir) self.fastapi_stopped = True log.debug('Fastapi process created') @@ -53,7 +46,14 @@ def start(self, **kwargs): log.debug('Fastapi starting main loop') self.fastapi_stopped = False try: - self.srv.serve_forever() + uvicorn.run( + app, + host=self.uvi_ip_address, + port=self.uvi_port, + reload=self.uvi_reload, + debug=self.uvi_debug, + log_level=log.level, + workers=3) except ServiceExit: log.info('Service exit requested') self.fastapi_stopped = True diff --git a/src/ambianic/webapp/server/config_sources.py b/src/ambianic/webapp/server/config_sources.py index 6d3c977f..1fdabd79 100644 --- a/src/ambianic/webapp/server/config_sources.py +++ b/src/ambianic/webapp/server/config_sources.py @@ -1,5 +1,5 @@ -from werkzeug.exceptions import NotFound, BadRequest +from fastapi import HTTPException from ambianic import config import logging @@ -28,12 +28,12 @@ def validate(source_id, source): for prop in source_model: if prop not in source_keys: - raise BadRequest(f"missing property {prop}") + raise HTTPException(status_code=400, detail=f"missing property {prop}") if not isinstance(source[prop], source_model[prop]): - raise BadRequest(f"invalid type for {prop}") + raise HTTPException(status_code=400, detail=f"invalid type for {prop}") if source["type"] not in source_types: - raise BadRequest(f"type should be one of {source_types}") + raise HTTPException(status_code=400, detail=f"type should be one of {source_types}") return source @@ -43,15 +43,16 @@ def get(source_id): log.info("Get source_id=%s", source_id) if not source_id: - raise BadRequest("source id is empy") + raise HTTPException(status_code=400, detail="source id is empy") if not isinstance(source_id, str): - raise BadRequest("source id should be a string") + raise HTTPException(status_code=400, detail="source id should be a string") source = config.sources[source_id] if source is None: - raise NotFound("source not found") + raise HTTPException(status_code=404, detail="source not found") + return source diff --git a/tests/test_rest_api.py b/tests/test_rest_api.py index 66c2f86d..92ca8cfe 100644 --- a/tests/test_rest_api.py +++ b/tests/test_rest_api.py @@ -2,7 +2,7 @@ import yaml import pytest import pkg_resources -from ambianic.webapp.fastapi-app import app +from ambianic.webapp.fastapi_app import app from ambianic import config, __version__ import logging import os @@ -10,8 +10,6 @@ log = logging.getLogger(__name__) -test_client = TestClient(app) - def reset_config(): config.reload() @@ -29,13 +27,7 @@ def teardown_module(module): @pytest.fixture def client(): - - app.config['TESTING'] = True - - with test_client() as fclient: - # with app.app_context(): - # flaskr.init_db() - yield fclient + test_client = TestClient(app) def test_hello(client):