From d5e964ac25346d92c36c8ed80c6efe9378c8fd4b Mon Sep 17 00:00:00 2001 From: Lu Ken Date: Fri, 27 Dec 2024 14:45:11 +0800 Subject: [PATCH 1/2] fix python lint issue Signed-off-by: Lu Ken --- README.md | 4 +++- src/{gentrade-server => gentrade_server}/__init__.py | 0 src/{gentrade-server => gentrade_server}/auth.py | 12 +++++++++--- src/{gentrade-server => gentrade_server}/db.py | 9 +++++++++ src/{gentrade-server => gentrade_server}/main.py | 8 ++++++++ .../routers/__init__.py | 0 .../routers/public.py | 9 +++++++-- .../routers/secure.py | 8 +++++++- 8 files changed, 43 insertions(+), 7 deletions(-) rename src/{gentrade-server => gentrade_server}/__init__.py (100%) rename src/{gentrade-server => gentrade_server}/auth.py (62%) rename src/{gentrade-server => gentrade_server}/db.py (78%) rename src/{gentrade-server => gentrade_server}/main.py (71%) rename src/{gentrade-server => gentrade_server}/routers/__init__.py (100%) rename src/{gentrade-server => gentrade_server}/routers/public.py (50%) rename src/{gentrade-server => gentrade_server}/routers/secure.py (65%) diff --git a/README.md b/README.md index 915b472..a6d4490 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,6 @@ ```shell export PYTHONPATH=/src uvicorn gentrade-server.main:app --reload -``` \ No newline at end of file +or +python -m gentrade-server.main +``` diff --git a/src/gentrade-server/__init__.py b/src/gentrade_server/__init__.py similarity index 100% rename from src/gentrade-server/__init__.py rename to src/gentrade_server/__init__.py diff --git a/src/gentrade-server/auth.py b/src/gentrade_server/auth.py similarity index 62% rename from src/gentrade-server/auth.py rename to src/gentrade_server/auth.py index 70e6804..e20dc5c 100644 --- a/src/gentrade-server/auth.py +++ b/src/gentrade_server/auth.py @@ -1,12 +1,18 @@ +""" +Authorization module +""" from fastapi import Security, HTTPException, status from fastapi.security import APIKeyHeader from .db import check_api_key, get_user_from_api_key api_key_header = APIKeyHeader(name="X-API-Key") -def get_user(api_key_header: str = Security(api_key_header)): - if check_api_key(api_key_header): - user = get_user_from_api_key(api_key_header) +def get_user(header: str = Security(api_key_header)): + """ + Get user from the API key + """ + if check_api_key(header): + user = get_user_from_api_key(header) return user raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/src/gentrade-server/db.py b/src/gentrade_server/db.py similarity index 78% rename from src/gentrade-server/db.py rename to src/gentrade_server/db.py index 7266790..4d14754 100644 --- a/src/gentrade-server/db.py +++ b/src/gentrade_server/db.py @@ -1,3 +1,6 @@ +""" +Database +""" api_keys = { "e54d4431-5dab-474e-b71a-0db1fcb9e659": "7oDYjo3d9r58EJKYi5x4E8", "5f0c7127-3be9-4488-b801-c7b6415b45e9": "mUP7PpTHmFAkxcQLWKMY8t" @@ -13,7 +16,13 @@ } def check_api_key(api_key: str): + """ + Check whether API key valid + """ return api_key in api_keys def get_user_from_api_key(api_key: str): + """ + Get the user from the given API key + """ return users[api_keys[api_key]] diff --git a/src/gentrade-server/main.py b/src/gentrade_server/main.py similarity index 71% rename from src/gentrade-server/main.py rename to src/gentrade_server/main.py index 9a67f98..ecfb774 100644 --- a/src/gentrade-server/main.py +++ b/src/gentrade_server/main.py @@ -1,3 +1,8 @@ +""" +The main entry +""" +import uvicorn + from fastapi import FastAPI, Depends from .routers import secure, public from .auth import get_user @@ -13,3 +18,6 @@ prefix="/api/v1/secure", dependencies=[Depends(get_user)] ) + +if __name__ == '__main__': + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/src/gentrade-server/routers/__init__.py b/src/gentrade_server/routers/__init__.py similarity index 100% rename from src/gentrade-server/routers/__init__.py rename to src/gentrade_server/routers/__init__.py diff --git a/src/gentrade-server/routers/public.py b/src/gentrade_server/routers/public.py similarity index 50% rename from src/gentrade-server/routers/public.py rename to src/gentrade_server/routers/public.py index 88373eb..55911cf 100644 --- a/src/gentrade-server/routers/public.py +++ b/src/gentrade_server/routers/public.py @@ -1,8 +1,13 @@ +""" +Public result API interfaces +""" from fastapi import APIRouter - router = APIRouter() @router.get("/") async def get_testroute(): - return "OK" \ No newline at end of file + """ + Test public interface + """ + return "OK" diff --git a/src/gentrade-server/routers/secure.py b/src/gentrade_server/routers/secure.py similarity index 65% rename from src/gentrade-server/routers/secure.py rename to src/gentrade_server/routers/secure.py index 4633323..d47347a 100644 --- a/src/gentrade-server/routers/secure.py +++ b/src/gentrade_server/routers/secure.py @@ -1,3 +1,6 @@ +""" +Secure API interface +""" from fastapi import APIRouter, Depends from ..auth import get_user @@ -5,4 +8,7 @@ @router.get("/") async def get_testroute(user: dict = Depends(get_user)): - return user \ No newline at end of file + """ + Test secure interface + """ + return user From 5efe79981caeeb3b9bf12e465b85602d4622d497 Mon Sep 17 00:00:00 2001 From: Lu Ken Date: Fri, 27 Dec 2024 17:58:57 +0800 Subject: [PATCH 2/2] api: add health and server_time Signed-off-by: Lu Ken --- requirements.txt | 6 +++- src/gentrade_server/config.py | 12 +++++++ src/gentrade_server/main.py | 28 ++++++++++++++- src/gentrade_server/routers/public.py | 52 +++++++++++++++++++++++++++ src/gentrade_server/util.py | 32 +++++++++++++++++ 5 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/gentrade_server/config.py create mode 100644 src/gentrade_server/util.py diff --git a/requirements.txt b/requirements.txt index 0195b38..0dad6d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,6 @@ fastapi[standard] -uvicorn \ No newline at end of file +uvicorn +pydantic +pydantic-settings +ntplib +python-dateutil diff --git a/src/gentrade_server/config.py b/src/gentrade_server/config.py new file mode 100644 index 0000000..9523d06 --- /dev/null +++ b/src/gentrade_server/config.py @@ -0,0 +1,12 @@ +""" +Configure +""" +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + """ + Settings + """ + ntp_server: str = "ntp.ntsc.ac.cn" + +settings = Settings() diff --git a/src/gentrade_server/main.py b/src/gentrade_server/main.py index ecfb774..3d9f134 100644 --- a/src/gentrade_server/main.py +++ b/src/gentrade_server/main.py @@ -1,13 +1,39 @@ """ The main entry """ -import uvicorn +import logging +from contextlib import asynccontextmanager +import uvicorn from fastapi import FastAPI, Depends +from fastapi.middleware.cors import CORSMiddleware + from .routers import secure, public from .auth import get_user +from .util import sync_ntp_server + +logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') +LOG = logging.getLogger(__name__) + +@asynccontextmanager +async def lifespan(_:FastAPI): + """ + App lifecycle + """ + LOG.info("Starting Up...") + sync_ntp_server() + yield + LOG.info("Shutting Down...") app = FastAPI() +app = FastAPI(lifespan=lifespan) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) app.include_router( public.router, diff --git a/src/gentrade_server/routers/public.py b/src/gentrade_server/routers/public.py index 55911cf..9cc10dd 100644 --- a/src/gentrade_server/routers/public.py +++ b/src/gentrade_server/routers/public.py @@ -1,7 +1,19 @@ """ Public result API interfaces """ +import logging + +import time +import datetime +from dateutil.tz import tzlocal + from fastapi import APIRouter +from pydantic import BaseModel + +from ..util import sync_ntp_server +from ..config import settings + +LOG = logging.getLogger(__name__) router = APIRouter() @@ -11,3 +23,43 @@ async def get_testroute(): Test public interface """ return "OK" + +class HealthCheck(BaseModel): + """ + Response model to validate and return when performing a health check. + """ + + status: str = "OK" + +@router.get("/health") +async def get_health() -> HealthCheck: + """ + Check health + """ + return HealthCheck(status="OK") + +@router.get("/settings") +async def get_settings(): + """ + Get server settings + """ + return { + 'ntp_server': settings.ntp_server + } + +@router.get("/server_time") +async def get_server_time(): + """ + Get server time + """ + curr_ts = time.time() + now_utc = datetime.datetime.fromtimestamp(curr_ts, datetime.UTC) + tl = tzlocal() + ntp_offset = sync_ntp_server() + + return { + 'ntp_offset': ntp_offset, + 'timezone_name': tl.tzname(now_utc), + 'timezone_offset': tl.utcoffset(now_utc).total_seconds(), + 'timestamp_server': int(curr_ts) + } diff --git a/src/gentrade_server/util.py b/src/gentrade_server/util.py new file mode 100644 index 0000000..913fa67 --- /dev/null +++ b/src/gentrade_server/util.py @@ -0,0 +1,32 @@ +""" +Utils +""" +import logging +import ntplib + +LOG = logging.getLogger(__name__) + +def sync_ntp_server() -> float: + """ + Sync with NTP server + + :return : offset in seconds + """ + ntp_servers = ['ntp.ntsc.ac.cn', 'ntp.sjtu.edu.cn', 'cn.ntp.org.cn', + 'cn.pool.ntp.org', 'ntp.aliyun.com'] + retry = len(ntp_servers) - 1 + client = ntplib.NTPClient() + while retry > 0: + LOG.info("Try to get time from NTP: %s", ntp_servers[retry]) + + try: + ret = client.request(ntp_servers[retry], version=3) + offset = (ret.recv_time - ret.orig_time + + ret.dest_time - ret.tx_time) / 2 + LOG.info("NTP offset: %.2f", offset) + return offset + except ntplib.NTPException: + LOG.error("Fail to get time, try another") + retry -= 1 + continue + return None