From 1e38fec61b42c998111297a56b8e2c7b2a304919 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Dec 2020 06:55:19 +0100 Subject: [PATCH] Initial fastapi implementation (Ping working) --- freqtrade/rpc/api_server2/__init__.py | 2 + freqtrade/rpc/api_server2/api_v1.py | 14 +++++ freqtrade/rpc/api_server2/uvicorn_threaded.py | 28 ++++++++++ freqtrade/rpc/api_server2/webserver.py | 56 +++++++++++++++++++ freqtrade/rpc/rpc_manager.py | 5 +- requirements.txt | 4 ++ 6 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 freqtrade/rpc/api_server2/__init__.py create mode 100644 freqtrade/rpc/api_server2/api_v1.py create mode 100644 freqtrade/rpc/api_server2/uvicorn_threaded.py create mode 100644 freqtrade/rpc/api_server2/webserver.py diff --git a/freqtrade/rpc/api_server2/__init__.py b/freqtrade/rpc/api_server2/__init__.py new file mode 100644 index 000000000..df255c186 --- /dev/null +++ b/freqtrade/rpc/api_server2/__init__.py @@ -0,0 +1,2 @@ +# flake8: noqa: F401 +from .webserver import ApiServer diff --git a/freqtrade/rpc/api_server2/api_v1.py b/freqtrade/rpc/api_server2/api_v1.py new file mode 100644 index 000000000..1e8bae1d4 --- /dev/null +++ b/freqtrade/rpc/api_server2/api_v1.py @@ -0,0 +1,14 @@ +from typing import Dict + +from fastapi import APIRouter + + +router = APIRouter() + + +@router.get('/ping') +def _ping() -> Dict[str, str]: + """simple ping version""" + return {"status": "pong"} + + diff --git a/freqtrade/rpc/api_server2/uvicorn_threaded.py b/freqtrade/rpc/api_server2/uvicorn_threaded.py new file mode 100644 index 000000000..ba9263620 --- /dev/null +++ b/freqtrade/rpc/api_server2/uvicorn_threaded.py @@ -0,0 +1,28 @@ +import contextlib +import time +import threading +import uvicorn + + +class UvicornServer(uvicorn.Server): + """ + Multithreaded server - as found in https://github.com/encode/uvicorn/issues/742 + """ + def install_signal_handlers(self): + pass + + @contextlib.contextmanager + def run_in_thread(self): + self.thread = threading.Thread(target=self.run) + self.thread.start() + # try: + while not self.started: + time.sleep(1e-3) + # yield + # finally: + # self.should_exit = True + # thread.join() + + def cleanup(self): + self.should_exit = True + self.thread.join() diff --git a/freqtrade/rpc/api_server2/webserver.py b/freqtrade/rpc/api_server2/webserver.py new file mode 100644 index 000000000..3f9baeaff --- /dev/null +++ b/freqtrade/rpc/api_server2/webserver.py @@ -0,0 +1,56 @@ +import threading +from typing import Any, Dict + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +import uvicorn + +from freqtrade.rpc.fiat_convert import CryptoToFiatConverter +from freqtrade.rpc.rpc import RPCHandler, RPC + +from .uvicorn_threaded import UvicornServer + + +class ApiServer(RPCHandler): + + def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None: + super().__init__(rpc, config) + self._server = None + + self.app = FastAPI() + self.configure_app(self.app, self._config) + + self.start_api() + + def cleanup(self) -> None: + """ Cleanup pending module resources """ + if self._server: + self._server.cleanup() + + def send_msg(self, msg: Dict[str, str]) -> None: + pass + + def configure_app(self, app, config): + from .api_v1 import router as api_v1 + app.include_router(api_v1, prefix="/api/v1") + + app.add_middleware( + CORSMiddleware, + allow_origins=config['api_server'].get('CORS_origins', []), + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + def start_api(self): + """ + Start API ... should be run in thread. + """ + uvconfig = uvicorn.Config(self.app, + port=self._config['api_server'].get('listen_port', 8080), + host=self._config['api_server'].get( + 'listen_ip_address', '127.0.0.1'), + access_log=True) + self._server = UvicornServer(uvconfig) + + self._server.run_in_thread() diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 38a4e95fd..2afd39eda 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -34,7 +34,10 @@ class RPCManager: # Enable local rest api server for cmd line control if config.get('api_server', {}).get('enabled', False): logger.info('Enabling rpc.api_server') - from freqtrade.rpc.api_server import ApiServer + # from freqtrade.rpc.api_server import ApiServer + # TODO: Remove the above import + from freqtrade.rpc.api_server2 import ApiServer + self.registered_modules.append(ApiServer(self._rpc, config)) def cleanup(self) -> None: diff --git a/requirements.txt b/requirements.txt index bab13ed03..ad43c1006 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,6 +32,10 @@ flask==1.1.2 flask-jwt-extended==3.25.0 flask-cors==3.0.9 +# API Server +fastapi==0.63.0 +uvicorn==0.13.2 + # Support for colorized terminal output colorama==0.4.4 # Building config files interactively