From de82f7a896855940d9daa35cb13371ec5118ab4f Mon Sep 17 00:00:00 2001 From: Max Wang Date: Fri, 4 Oct 2024 11:35:48 -0400 Subject: [PATCH 1/5] Remove async endpoint --- app/server.py | 50 -------------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/app/server.py b/app/server.py index 0dccd15..530457e 100644 --- a/app/server.py +++ b/app/server.py @@ -113,56 +113,6 @@ } } -ASYNC_EXAMPLE = { - "callback": "http://test", - **EXAMPLE, -} - - -async def async_appraise(message, callback, logger: logging.Logger): - try: - await get_ordering_components(message, logger) - except Exception: - logger.error(f"Something went wrong while appraising: {traceback.format_exc()}") - logger.info("Done appraising") - try: - logger.info(f"Posting to callback {callback}") - async with httpx.AsyncClient(timeout=httpx.Timeout(timeout=600.0)) as client: - res = await client.post(callback, json=message) - logger.info(f"Posted to {callback} with code {res.status_code}") - except Exception as e: - logger.error(f"Unable to post to callback {callback}.") - - -@APP.post("/async_get_appraisal", response_model=AsyncQueryResponse) -async def get_appraisal( - background_tasks: BackgroundTasks, - query: AsyncQuery = Body(..., example=ASYNC_EXAMPLE), -): - """Appraise Answers""" - qid = str(uuid4())[:8] - query_dict = query.dict() - log_level = query_dict.get("log_level") or "INFO" - logger = get_logger(qid, log_level) - logger.info("Starting async appraisal") - message = query_dict["message"] - if not message.get("results"): - logger.warning("No results given.") - return JSONResponse( - content={"status": "Rejected", "description": "No Results.", "job_id": qid}, - status_code=400, - ) - callback = query_dict["callback"] - background_tasks.add_task(async_appraise, message, callback, logger) - return JSONResponse( - content={ - "status": "Accepted", - "description": f"Appraising answers. Will send response to {callback}", - "job_id": qid, - }, - status_code=200, - ) - @APP.post("/get_appraisal") async def sync_get_appraisal(query=Body(..., example=EXAMPLE)): From e105cbd6f2c54e041c05f20a0e5b5ae7569014ac Mon Sep 17 00:00:00 2001 From: Max Wang Date: Fri, 4 Oct 2024 11:36:34 -0400 Subject: [PATCH 2/5] Add compression --- app/server.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/app/server.py b/app/server.py index 530457e..c442692 100644 --- a/app/server.py +++ b/app/server.py @@ -1,15 +1,14 @@ +import gzip +import json import logging import redis import traceback -from fastapi import Body, BackgroundTasks, HTTPException, status -from fastapi.responses import JSONResponse -import httpx +from fastapi import HTTPException, status, Request +from fastapi.responses import JSONResponse, Response from starlette.middleware.cors import CORSMiddleware from uuid import uuid4 -from reasoner_pydantic import AsyncQuery, AsyncQueryResponse, Response, Query - from .config import settings from .logger import setup_logger, get_logger from .trapi import TRAPI @@ -115,12 +114,25 @@ @APP.post("/get_appraisal") -async def sync_get_appraisal(query=Body(..., example=EXAMPLE)): +async def sync_get_appraisal(request: Request): qid = str(uuid4())[:8] - # query_dict = query.dict() - log_level = query.get("log_level") or "INFO" - logger = get_logger(qid, log_level) + logger = get_logger(qid, "INFO") logger.info("Starting sync appraisal") + compressed = False + if request.headers.get("content-encoding") == "gzip": + try: + raw_body = await request.body() + query = json.loads(gzip.decompress(raw_body)) + compressed = True + except Exception: + return Response("Invalid request. Failed to decompress and ingest.", 400) + else: + try: + query = await request.json() + except json.JSONDecodeError: + return Response("Invalid request. Failed to parse JSON.", 400) + log_level = query.get("log_level") or "INFO" + logger.setLevel(log_level) message = query["message"] if not message.get("results"): return JSONResponse( @@ -131,8 +143,12 @@ async def sync_get_appraisal(query=Body(..., example=EXAMPLE)): await get_ordering_components(message, logger) except Exception: logger.error(f"Something went wrong while appraising: {traceback.format_exc()}") + if compressed: + query = gzip.compress(json.dumps(query).encode()) + else: + query = json.dumps(query) logger.info("Done appraising") - return query + return Response(query) @APP.get("/redis_ready") From 58f6caaa3a2da1086d15c812df7f4cdfabb79312 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Fri, 4 Oct 2024 13:08:07 -0400 Subject: [PATCH 3/5] Fix up versions and python version in docker --- Dockerfile | 2 +- Dockerfile.test | 2 +- app/config.py | 4 ++++ app/server.py | 36 ++++++++++++++++++++++++++++++++++++ manage.py | 8 ++++---- requirements-lock.txt | 19 +++++++++++++++++++ requirements-test-lock.txt | 2 ++ requirements-test.txt | 8 ++++---- requirements.txt | 24 ++++++++++++++---------- 9 files changed, 85 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index e4c275e..923ad2e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM renciorg/renci-python-image:v0.0.1 +FROM ghcr.io/translatorsri/renci-python-image:3.12.4 # Add image info LABEL org.opencontainers.image.source /github.com/TranslatorSRI/answer-appraiser diff --git a/Dockerfile.test b/Dockerfile.test index fd5901b..62c2b76 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,4 +1,4 @@ -FROM renciorg/renci-python-image:v0.0.1 +FROM ghcr.io/translatorsri/renci-python-image:3.12.4 # Add image info LABEL org.opencontainers.image.source /github.com/TranslatorSRI/answer-appraiser diff --git a/app/config.py b/app/config.py index 23576e1..781498c 100644 --- a/app/config.py +++ b/app/config.py @@ -12,6 +12,10 @@ class Settings(BaseSettings): redis_port: int = 6379 redis_password: str = "supersecretpassword" + jaeger_enabled: str = "True" + jaeger_host: str = "jaeger" + jaeger_port: int = 6831 + class Config: env_file = ".env" diff --git a/app/server.py b/app/server.py index c442692..b76594c 100644 --- a/app/server.py +++ b/app/server.py @@ -1,8 +1,10 @@ import gzip import json import logging +import os import redis import traceback +import warnings from fastapi import HTTPException, status, Request from fastapi.responses import JSONResponse, Response @@ -52,6 +54,40 @@ allow_headers=["*"], ) +if settings.jaeger_enabled == "True": + LOGGER.info("Starting up Jaeger") + + from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + from opentelemetry import trace + from opentelemetry.exporter.jaeger.thrift import JaegerExporter + from opentelemetry.sdk.resources import ( + SERVICE_NAME, + Resource, + ) + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor + + # httpx connections need to be open a little longer by the otel decorators + # but some libs display warnings of resource being unclosed. + # these supresses such warnings. + logging.captureWarnings(capture=True) + warnings.filterwarnings("ignore", category=ResourceWarning) + service_name = os.environ.get("OTEL_SERVICE_NAME", "ANSWER-APPRAISER") + jaeger_exporter = JaegerExporter( + agent_host_name=settings.jaeger_host, + agent_port=int(settings.jaeger_port), + ) + resource = Resource(attributes={SERVICE_NAME: service_name}) + provider = TracerProvider(resource=resource) + processor = BatchSpanProcessor(jaeger_exporter) + provider.add_span_processor(processor) + trace.set_tracer_provider(provider) + FastAPIInstrumentor.instrument_app( + APP, tracer_provider=provider, excluded_urls="docs,openapi.json" + ) + HTTPXClientInstrumentor().instrument() + EXAMPLE = { "message": { "query_graph": { diff --git a/manage.py b/manage.py index a1590e1..8a114c7 100755 --- a/manage.py +++ b/manage.py @@ -53,7 +53,7 @@ def lock(extra_args): """ for src, locked in REQUIREMENTS_FILES.items(): command = f"""\ - docker run -v $(pwd):/app python:3.9 \ + docker run -v $(pwd):/app python:3.12 \ /bin/bash -c " # Install lockfile first so that we get the # currently installed versions of dependencies @@ -74,7 +74,7 @@ def verify_locked(extra_args): for src, locked in REQUIREMENTS_FILES.items(): dependencies = get_command_output( f"""\ - docker run -v $(pwd):/app python:3.9 \ + docker run -v $(pwd):/app python:3.12 \ /bin/bash -c " pip install -qqq -r /app/{locked} && pip install -qqq -r /app/{src} && @@ -84,7 +84,7 @@ def verify_locked(extra_args): ) lock_dependencies = get_command_output( f"""\ - docker run -v $(pwd):/app python:3.9 \ + docker run -v $(pwd):/app python:3.12 \ /bin/bash -c " pip install -qqq -r /app/{locked} && pip freeze @@ -102,7 +102,7 @@ def upgrade(extra_args): """ for src, locked in REQUIREMENTS_FILES.items(): command = f"""\ - docker run -v $(pwd):/app python:3.9 \ + docker run -v $(pwd):/app python:3.12 \ /bin/bash -c " # Install dependencies, getting latest version pip install -r /app/{src} && diff --git a/requirements-lock.txt b/requirements-lock.txt index 770c64c..380fce0 100644 --- a/requirements-lock.txt +++ b/requirements-lock.txt @@ -3,17 +3,33 @@ asgiref==3.7.2 async-timeout==4.0.2 certifi==2023.5.7 click==7.1.2 +Deprecated==1.2.14 exceptiongroup==1.1.1 fastapi==0.75.0 +googleapis-common-protos==1.59.1 +grpcio==1.66.2 gunicorn==20.1.0 h11==0.14.0 httpcore==0.17.2 httptools==0.2.0 httpx==0.24.1 idna==3.4 +importlib_metadata==8.4.0 numpy==1.25.1 +opentelemetry-api==1.27.0 +opentelemetry-exporter-jaeger==1.21.0 +opentelemetry-exporter-jaeger-proto-grpc==1.21.0 +opentelemetry-exporter-jaeger-thrift==1.21.0 +opentelemetry-instrumentation==0.48b0 +opentelemetry-instrumentation-asgi==0.48b0 +opentelemetry-instrumentation-fastapi==0.48b0 +opentelemetry-instrumentation-httpx==0.48b0 +opentelemetry-sdk==1.27.0 +opentelemetry-semantic-conventions==0.48b0 +opentelemetry-util-http==0.48b0 pandas==2.0.3 Pillow==10.0.0 +protobuf==4.25.5 pydantic==1.10.9 python-dateutil==2.8.2 python-dotenv==1.0.0 @@ -25,6 +41,7 @@ redis==4.6.0 six==1.16.0 sniffio==1.3.0 starlette==0.17.1 +thrift==0.21.0 tqdm==4.65.0 typing_extensions==4.6.3 tzdata==2023.3 @@ -32,3 +49,5 @@ uvicorn==0.13.3 uvloop==0.17.0 watchgod==0.8.2 websockets==11.0.3 +wrapt==1.16.0 +zipp==3.20.2 diff --git a/requirements-test-lock.txt b/requirements-test-lock.txt index 56825fa..37d75bb 100644 --- a/requirements-test-lock.txt +++ b/requirements-test-lock.txt @@ -14,6 +14,8 @@ pytest-asyncio==0.16.0 pytest-cov==2.11.1 redis==4.6.0 requests==2.25.1 +setuptools==70.0.0 sortedcontainers==2.4.0 toml==0.10.2 urllib3==1.26.16 +wheel==0.43.0 diff --git a/requirements-test.txt b/requirements-test.txt index 76c60f5..645b4bb 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,4 @@ -fakeredis==2.17.0 -pytest==6.2.2 -pytest-cov==2.11.1 -requests==2.25.1 \ No newline at end of file +fakeredis +pytest +pytest-cov +requests \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b9cd09a..1777fdc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,14 @@ -fastapi==0.75.0 -gunicorn==20.1.0 -httpx==0.24.1 -numpy==1.25.1 -pandas==2.0.3 -rdkit==2023.3.2 -reasoner-pydantic==5.0.3 -redis==4.6.0 -tqdm==4.65.0 -uvicorn==0.13.3 \ No newline at end of file +fastapi +gunicorn +httpx +numpy +opentelemetry-sdk +opentelemetry-instrumentation-fastapi +opentelemetry-exporter-jaeger +opentelemetry-instrumentation-httpx +pandas +rdkit +reasoner-pydantic +redis +tqdm +uvicorn \ No newline at end of file From 6a498c1eb30b5379ddda524557f3282bfb7e10d2 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Fri, 4 Oct 2024 13:09:06 -0400 Subject: [PATCH 4/5] Bump patch version --- app/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server.py b/app/server.py index b76594c..ab9877d 100644 --- a/app/server.py +++ b/app/server.py @@ -22,7 +22,7 @@ openapi_args = dict( title="SRI Answer Appraiser", - version="0.6.0", + version="0.6.1", terms_of_service="", description="SRI service that provides metrics for scoring and ordering of results", trapi="1.5.0", From 7ac4f9a3aabd0e541fa8b96a884f57a28902cd82 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Fri, 4 Oct 2024 13:16:43 -0400 Subject: [PATCH 5/5] Update requirement lock files --- requirements-lock.txt | 59 +++++++++++++++++--------------------- requirements-test-lock.txt | 29 ++++++++----------- 2 files changed, 39 insertions(+), 49 deletions(-) diff --git a/requirements-lock.txt b/requirements-lock.txt index 380fce0..362fee8 100644 --- a/requirements-lock.txt +++ b/requirements-lock.txt @@ -1,21 +1,18 @@ -anyio==3.7.0 -asgiref==3.7.2 -async-timeout==4.0.2 -certifi==2023.5.7 -click==7.1.2 +anyio==4.6.0 +asgiref==3.8.1 +certifi==2024.8.30 +click==8.1.7 Deprecated==1.2.14 -exceptiongroup==1.1.1 -fastapi==0.75.0 +fastapi==0.115.0 googleapis-common-protos==1.59.1 grpcio==1.66.2 -gunicorn==20.1.0 +gunicorn==23.0.0 h11==0.14.0 -httpcore==0.17.2 -httptools==0.2.0 -httpx==0.24.1 -idna==3.4 +httpcore==1.0.6 +httpx==0.27.2 +idna==3.10 importlib_metadata==8.4.0 -numpy==1.25.1 +numpy==2.1.1 opentelemetry-api==1.27.0 opentelemetry-exporter-jaeger==1.21.0 opentelemetry-exporter-jaeger-proto-grpc==1.21.0 @@ -27,27 +24,25 @@ opentelemetry-instrumentation-httpx==0.48b0 opentelemetry-sdk==1.27.0 opentelemetry-semantic-conventions==0.48b0 opentelemetry-util-http==0.48b0 -pandas==2.0.3 -Pillow==10.0.0 +packaging==24.1 +pandas==2.2.3 +pillow==10.4.0 protobuf==4.25.5 -pydantic==1.10.9 -python-dateutil==2.8.2 -python-dotenv==1.0.0 -pytz==2023.3 -PyYAML==6.0 -rdkit==2023.3.2 -reasoner-pydantic==5.0.3 -redis==4.6.0 +pydantic==1.10.18 +python-dateutil==2.9.0.post0 +pytz==2024.2 +rdkit==2024.3.5 +reasoner-pydantic==5.0.6 +redis==5.1.1 +setuptools==70.0.0 six==1.16.0 -sniffio==1.3.0 -starlette==0.17.1 +sniffio==1.3.1 +starlette==0.38.6 thrift==0.21.0 -tqdm==4.65.0 -typing_extensions==4.6.3 -tzdata==2023.3 -uvicorn==0.13.3 -uvloop==0.17.0 -watchgod==0.8.2 -websockets==11.0.3 +tqdm==4.66.5 +typing_extensions==4.12.2 +tzdata==2024.2 +uvicorn==0.31.0 +wheel==0.43.0 wrapt==1.16.0 zipp==3.20.2 diff --git a/requirements-test-lock.txt b/requirements-test-lock.txt index 37d75bb..24b5655 100644 --- a/requirements-test-lock.txt +++ b/requirements-test-lock.txt @@ -1,21 +1,16 @@ -async-timeout==4.0.2 -attrs==23.1.0 -certifi==2023.5.7 -chardet==4.0.0 -coverage==7.2.7 -fakeredis==2.17.0 -idna==2.10 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage==7.6.1 +fakeredis==2.25.1 +idna==3.10 iniconfig==2.0.0 -packaging==23.1 -pluggy==0.13.1 -py==1.11.0 -pytest==6.2.2 -pytest-asyncio==0.16.0 -pytest-cov==2.11.1 -redis==4.6.0 -requests==2.25.1 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +redis==5.1.1 +requests==2.32.3 setuptools==70.0.0 sortedcontainers==2.4.0 -toml==0.10.2 -urllib3==1.26.16 +urllib3==2.2.3 wheel==0.43.0