diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b5f653c..6124944 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -83,7 +83,7 @@ jobs: -p 5003:8765 \ ${{ env.IMAGE }}-final:latest - name: Install requirements - run: docker exec fastapi-tdd pip install black==23.12.1 flake8==7.0.0 isort==5.13.2 pytest==7.4.4 + run: docker exec fastapi-tdd python3 -m pip install black==25.1.0 flake8==7.2.0 isort==6.0.1 pytest==8.3.5 - name: Pytest run: docker exec fastapi-tdd python -m pytest . - name: Flake8 @@ -98,7 +98,7 @@ jobs: runs-on: ubuntu-latest needs: [build, test] env: - HEROKU_APP_NAME: quiet-citadel-80656 + HEROKU_APP_NAME: shrouded-shelf-89730 HEROKU_REGISTRY_IMAGE: registry.heroku.com/${HEROKU_APP_NAME}/summarizer steps: - name: Checkout diff --git a/docker-compose.yml b/docker-compose.yml index 2881e99..8e5fb26 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: web: diff --git a/project/Dockerfile b/project/Dockerfile index cb8c488..53c7c3a 100644 --- a/project/Dockerfile +++ b/project/Dockerfile @@ -1,5 +1,5 @@ # pull official base image -FROM python:3.12.1-slim-bookworm +FROM python:3.13.3-slim-bookworm # set working directory WORKDIR /usr/src/app diff --git a/project/Dockerfile.prod b/project/Dockerfile.prod index 3832eff..f7fae36 100644 --- a/project/Dockerfile.prod +++ b/project/Dockerfile.prod @@ -3,7 +3,7 @@ ########### # pull official base image -FROM python:3.12.1-slim-bookworm as builder +FROM python:3.13.3-slim-bookworm as builder # install system dependencies RUN apt-get update \ @@ -24,7 +24,7 @@ RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requir # lint COPY . /usr/src/app/ -RUN pip install black==23.12.1 flake8==7.0.0 isort==5.13.2 +RUN pip install black==25.1.0 flake8==7.2.0 isort==6.0.1 RUN flake8 . RUN black --exclude=migrations . --check RUN isort . --check-only @@ -35,7 +35,7 @@ RUN isort . --check-only ######### # pull official base image -FROM python:3.12.1-slim-bookworm +FROM python:3.13.3-slim-bookworm # create directory for the app user RUN mkdir -p /home/app @@ -65,7 +65,7 @@ COPY --from=builder /usr/src/app/wheels /wheels COPY --from=builder /usr/src/app/requirements.txt . RUN pip install --upgrade pip RUN pip install --no-cache /wheels/* -RUN pip install "uvicorn[standard]==0.26.0" +RUN pip install "uvicorn[standard]==0.34.1" # add app COPY . . diff --git a/project/app/api/crud.py b/project/app/api/crud.py index 9b58efb..0af078b 100644 --- a/project/app/api/crud.py +++ b/project/app/api/crud.py @@ -1,8 +1,12 @@ from typing import List, Union -from app.models.pydantic import SummaryPayloadSchema from app.models.tortoise import TextSummary +from app.models.pydantic import ( # isort:skip + SummaryPayloadSchema, + SummaryUpdatePayloadSchema, +) + async def get(id: int) -> Union[dict, None]: summary = await TextSummary.filter(id=id).first().values() @@ -22,7 +26,7 @@ async def post(payload: SummaryPayloadSchema) -> int: return summary.id -async def put(id: int, payload: SummaryPayloadSchema) -> Union[dict, None]: +async def put(id: int, payload: SummaryUpdatePayloadSchema) -> Union[dict, None]: summary = await TextSummary.filter(id=id).update( url=payload.url, summary=payload.summary ) diff --git a/project/app/main.py b/project/app/main.py index 4718867..4d1327f 100644 --- a/project/app/main.py +++ b/project/app/main.py @@ -20,13 +20,4 @@ def create_application() -> FastAPI: app = create_application() - -@app.on_event("startup") -async def startup_event(): - log.info("Starting up...") - init_db(app) - - -@app.on_event("shutdown") -async def shutdown_event(): - log.info("Shutting down...") +init_db(app) diff --git a/project/app/summarizer.py b/project/app/summarizer.py index 85c794a..a2c02db 100644 --- a/project/app/summarizer.py +++ b/project/app/summarizer.py @@ -10,9 +10,9 @@ async def generate_summary(summary_id: int, url: str) -> None: article.parse() try: - nltk.data.find("tokenizers/punkt") + nltk.data.find("tokenizers/punkt_tab") except LookupError: - nltk.download("punkt") + nltk.download("punkt_tab") finally: article.nlp() diff --git a/project/db/Dockerfile b/project/db/Dockerfile index 69c75af..9745248 100644 --- a/project/db/Dockerfile +++ b/project/db/Dockerfile @@ -1,5 +1,5 @@ # pull official base image -FROM postgres:16 +FROM postgres:17 # run create.sql on init ADD create.sql /docker-entrypoint-initdb.d diff --git a/project/migrations/models/0_20240122171233_init.py b/project/migrations/models/0_20250421234125_init.py similarity index 88% rename from project/migrations/models/0_20240122171233_init.py rename to project/migrations/models/0_20250421234125_init.py index 0172d9a..8672d53 100644 --- a/project/migrations/models/0_20240122171233_init.py +++ b/project/migrations/models/0_20250421234125_init.py @@ -7,7 +7,7 @@ async def upgrade(db: BaseDBAsyncClient) -> str: "id" SERIAL NOT NULL PRIMARY KEY, "url" TEXT NOT NULL, "summary" TEXT NOT NULL, - "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP + "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS "aerich" ( "id" SERIAL NOT NULL PRIMARY KEY, diff --git a/project/requirements-dev.txt b/project/requirements-dev.txt index 475b437..7b8577c 100644 --- a/project/requirements-dev.txt +++ b/project/requirements-dev.txt @@ -1,8 +1,8 @@ -black==23.12.1 -flake8==7.0.0 -isort==5.13.2 -pytest==7.4.4 -pytest-cov==4.1.0 -pytest-xdist==3.5.0 +black==25.1.0 +flake8==7.2.0 +isort==6.0.1 +pytest==8.3.5 +pytest-cov==6.1.1 +pytest-xdist==3.6.1 -r requirements.txt diff --git a/project/requirements.txt b/project/requirements.txt index 9176370..d531206 100644 --- a/project/requirements.txt +++ b/project/requirements.txt @@ -1,9 +1,10 @@ -aerich==0.7.2 -asyncpg==0.29.0 -fastapi==0.109.0 -gunicorn==21.0.1 -httpx==0.26.0 +aerich[toml]==0.8.2 +asyncpg==0.30.0 +fastapi==0.115.12 +gunicorn==22.0.0 +httpx==0.28.1 +lxml-html-clean==0.4.2 newspaper3k==0.2.8 -pydantic-settings==2.1.0 -tortoise-orm==0.20.0 -uvicorn==0.26.0 +pydantic-settings==2.8.1 +tortoise-orm==0.25.0 +uvicorn==0.34.1 diff --git a/project/tests/conftest.py b/project/tests/conftest.py index 29a9fd8..7e272c9 100644 --- a/project/tests/conftest.py +++ b/project/tests/conftest.py @@ -18,6 +18,7 @@ def test_app(): app = create_application() app.dependency_overrides[get_settings] = get_settings_override with TestClient(app) as test_client: + # testing yield test_client @@ -37,6 +38,7 @@ def test_app_with_db(): add_exception_handlers=True, ) with TestClient(app) as test_client: + # testing yield test_client diff --git a/project/tests/test_summaries.py b/project/tests/test_summaries.py index 16284f3..4c027fe 100644 --- a/project/tests/test_summaries.py +++ b/project/tests/test_summaries.py @@ -29,7 +29,6 @@ def test_create_summaries_invalid_json(test_app): "loc": ["body", "url"], "msg": "Field required", "type": "missing", - "url": "https://errors.pydantic.dev/2.5/v/missing", } ] } @@ -48,7 +47,7 @@ def mock_generate_summary(summary_id, url): monkeypatch.setattr(summaries, "generate_summary", mock_generate_summary) response = test_app_with_db.post( - "/summaries/", data=json.dumps({"url": "https://foo.bar"}) + "/summaries/", data=json.dumps({"url": "https://foo.bar/"}) ) summary_id = response.json()["id"] @@ -76,7 +75,6 @@ def test_read_summary_incorrect_id(test_app_with_db): "loc": ["path", "id"], "msg": "Input should be greater than 0", "type": "greater_than", - "url": "https://errors.pydantic.dev/2.5/v/greater_than", } ] } @@ -89,7 +87,7 @@ def mock_generate_summary(summary_id, url): monkeypatch.setattr(summaries, "generate_summary", mock_generate_summary) response = test_app_with_db.post( - "/summaries/", data=json.dumps({"url": "https://foo.bar"}) + "/summaries/", data=json.dumps({"url": "https://foo.bar/"}) ) summary_id = response.json()["id"] @@ -107,7 +105,7 @@ def mock_generate_summary(summary_id, url): monkeypatch.setattr(summaries, "generate_summary", mock_generate_summary) response = test_app_with_db.post( - "/summaries/", data=json.dumps({"url": "https://foo.bar"}) + "/summaries/", data=json.dumps({"url": "https://foo.bar/"}) ) summary_id = response.json()["id"] @@ -131,7 +129,6 @@ def test_remove_summary_incorrect_id(test_app_with_db): "loc": ["path", "id"], "msg": "Input should be greater than 0", "type": "greater_than", - "url": "https://errors.pydantic.dev/2.5/v/greater_than", } ] } @@ -144,13 +141,13 @@ def mock_generate_summary(summary_id, url): monkeypatch.setattr(summaries, "generate_summary", mock_generate_summary) response = test_app_with_db.post( - "/summaries/", data=json.dumps({"url": "https://foo.bar"}) + "/summaries/", data=json.dumps({"url": "https://foo.bar/"}) ) summary_id = response.json()["id"] response = test_app_with_db.put( f"/summaries/{summary_id}/", - data=json.dumps({"url": "https://foo.bar", "summary": "updated!"}), + data=json.dumps({"url": "https://foo.bar/", "summary": "updated!"}), ) assert response.status_code == 200 @@ -166,13 +163,13 @@ def mock_generate_summary(summary_id, url): [ [ 999, - {"url": "https://foo.bar", "summary": "updated!"}, + {"url": "https://foo.bar/", "summary": "updated!"}, 404, "Summary not found", ], [ 0, - {"url": "https://foo.bar", "summary": "updated!"}, + {"url": "https://foo.bar/", "summary": "updated!"}, 422, [ { @@ -181,7 +178,6 @@ def mock_generate_summary(summary_id, url): "msg": "Input should be greater than 0", "input": "0", "ctx": {"gt": 0}, - "url": "https://errors.pydantic.dev/2.5/v/greater_than", } ], ], @@ -195,28 +191,25 @@ def mock_generate_summary(summary_id, url): "loc": ["body", "url"], "msg": "Field required", "input": {}, - "url": "https://errors.pydantic.dev/2.5/v/missing", }, { "type": "missing", "loc": ["body", "summary"], "msg": "Field required", "input": {}, - "url": "https://errors.pydantic.dev/2.5/v/missing", }, ], ], [ 1, - {"url": "https://foo.bar"}, + {"url": "https://foo.bar/"}, 422, [ { "type": "missing", "loc": ["body", "summary"], "msg": "Field required", - "input": {"url": "https://foo.bar"}, - "url": "https://errors.pydantic.dev/2.5/v/missing", + "input": {"url": "https://foo.bar/"}, } ], ], diff --git a/project/tests/test_summaries_unit.py b/project/tests/test_summaries_unit.py index 9216b0c..84d41f2 100644 --- a/project/tests/test_summaries_unit.py +++ b/project/tests/test_summaries_unit.py @@ -39,7 +39,6 @@ def test_create_summaries_invalid_json(test_app): "loc": ["body", "url"], "msg": "Field required", "input": {}, - "url": "https://errors.pydantic.dev/2.5/v/missing", } ] } @@ -180,7 +179,6 @@ async def mock_put(id, payload): "msg": "Input should be greater than 0", "input": "0", "ctx": {"gt": 0}, - "url": "https://errors.pydantic.dev/2.5/v/greater_than", } ], ], @@ -194,14 +192,12 @@ async def mock_put(id, payload): "loc": ["body", "url"], "msg": "Field required", "input": {}, - "url": "https://errors.pydantic.dev/2.5/v/missing", }, { "type": "missing", "loc": ["body", "summary"], "msg": "Field required", "input": {}, - "url": "https://errors.pydantic.dev/2.5/v/missing", }, ], ], @@ -215,7 +211,6 @@ async def mock_put(id, payload): "loc": ["body", "summary"], "msg": "Field required", "input": {"url": "https://foo.bar"}, - "url": "https://errors.pydantic.dev/2.5/v/missing", } ], ],