From 9ce0ae11093105dd6a48a4e5b9dc9fa939912572 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sat, 15 Jun 2024 19:36:16 +0530 Subject: [PATCH 01/18] chore: removing mongodb stuff and just listening new updates --- .gitignore | 3 ++- docker-compose.yml | 22 ---------------------- src/__init__.py | 2 +- src/hackernews.py | 2 -- 4 files changed, 3 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 67f2704..d2f7f6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env __pycache__ .vscode -.idea \ No newline at end of file +.idea +.venv diff --git a/docker-compose.yml b/docker-compose.yml index 51910a5..24db08d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,27 +1,5 @@ -x-healthcheck-config: &healthcheck-config - interval: 10s - timeout: 10s - retries: 5 - services: app: build: context: . dockerfile: Dockerfile - depends_on: - - mongodb - - mongodb: - image: mongo:7.0.9 - ports: - - "27017:27017" - env_file: - - .env - healthcheck: - <<: *healthcheck-config - test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet - volumes: - - mongodb_data:/data/db - -volumes: - mongodb_data: diff --git a/src/__init__.py b/src/__init__.py index 850da19..844b817 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -18,7 +18,7 @@ async def main(): if latest_item_id is None: raise Exception("Unable to fetch latest item_id. Exiting") tasks = [ - asyncio.create_task(HackerNews.fetch_story_items(end_item=latest_item_id)), + # asyncio.create_task(HackerNews.fetch_story_items(end_item=latest_item_id)), asyncio.create_task(HackerNews.listen_updates()), ] await asyncio.gather(*tasks) diff --git a/src/hackernews.py b/src/hackernews.py index d6199e1..73423bc 100644 --- a/src/hackernews.py +++ b/src/hackernews.py @@ -29,7 +29,6 @@ async def get_latest_item(cls) -> int | None: @classmethod async def listen_updates(cls, batch_size=20): - await asyncio.sleep(30) while True: response = await fetch_url(url=urljoin(cls.base_url, "v0/updates.json")) items = response["items"] @@ -100,7 +99,6 @@ async def fetch_comments(cls, *, items: list[Item], batch_size=20000): comment_item_ids = [] for item in items: comment_item_ids.extend(item.kids) - print(f"Total comments found: {len(comment_item_ids)}") for i in range(0, len(comment_item_ids), batch_size): batched_comments = [ comment_item_ids[c_i] From 39a0b02750834c90bc0abf59a18f4340d52f1727 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sat, 15 Jun 2024 19:37:54 +0530 Subject: [PATCH 02/18] chore: updated README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb91eb1..2991571 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# HNScraper +# hckernews Scrapes relevant stories and their comments for learning. It scrapes historical stories and comments and also polls for new stories and extracts their comments. From f42ce5a1d4fffe53f9fad95f176d1b95210534b3 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sat, 15 Jun 2024 20:36:27 +0530 Subject: [PATCH 03/18] feat: update module for listening hackernews updates --- src/common/__init__.py | 0 src/{ => common}/db.py | 0 src/{ => common}/hackernews.py | 42 ++++------------------------ src/common/utils.py | 7 +++++ src/updates/__init__.py | 50 ++++++++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 src/common/__init__.py rename src/{ => common}/db.py (100%) rename src/{ => common}/hackernews.py (67%) create mode 100644 src/common/utils.py create mode 100644 src/updates/__init__.py diff --git a/src/common/__init__.py b/src/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/db.py b/src/common/db.py similarity index 100% rename from src/db.py rename to src/common/db.py diff --git a/src/hackernews.py b/src/common/hackernews.py similarity index 67% rename from src/hackernews.py rename to src/common/hackernews.py index 73423bc..f455194 100644 --- a/src/hackernews.py +++ b/src/common/hackernews.py @@ -1,15 +1,9 @@ import asyncio -import aiohttp from urllib.parse import urljoin from odmantic import AIOEngine -from src.db import Item, get_item, save_items - - -async def fetch_url(url: str): - async with aiohttp.ClientSession() as s: - async with s.get(url) as res: - return await res.json() +from .db import Item, get_item, save_items +from .utils import fetch_url class HackerNews: @@ -27,34 +21,6 @@ async def get_latest_item(cls) -> int | None: except Exception: return None - @classmethod - async def listen_updates(cls, batch_size=20): - while True: - response = await fetch_url(url=urljoin(cls.base_url, "v0/updates.json")) - items = response["items"] - for i in range(0, len(items), batch_size): - batched_items = [ - items[item_i] - for item_i in range(i, i + batch_size) - if item_i < len(items) - ] - items_task = [ - asyncio.create_task(cls.fetch_item(item_id)) - for item_id in batched_items - ] - items_responses = await asyncio.gather(*items_task) - story_items = [ - story_item - for story_item in items_responses - if story_item is not None and story_item["type"] == "story" - ] - items_instances = await save_items(engine=cls.engine, items=story_items) - await cls.fetch_comments(items=items_instances) - print( - f"Saved updated stories: {[story_item.id for story_item in items_instances]}" - ) - await asyncio.sleep(60) - @classmethod async def fetch_item(cls, item_id: int): try: @@ -112,3 +78,7 @@ async def fetch_comments(cls, *, items: list[Item], batch_size=20000): comment_responses = await asyncio.gather(*comment_tasks) comment_items = await save_items(engine=cls.engine, items=comment_responses) await cls.fetch_comments(items=comment_items) + + @classmethod + async def save_items(cls, items: list[dict | None]): + return await save_items(engine=HackerNews.engine, items=items) diff --git a/src/common/utils.py b/src/common/utils.py new file mode 100644 index 0000000..812d804 --- /dev/null +++ b/src/common/utils.py @@ -0,0 +1,7 @@ +import aiohttp + + +async def fetch_url(url: str): + async with aiohttp.ClientSession() as s: + async with s.get(url) as res: + return await res.json() \ No newline at end of file diff --git a/src/updates/__init__.py b/src/updates/__init__.py new file mode 100644 index 0000000..a7cb32b --- /dev/null +++ b/src/updates/__init__.py @@ -0,0 +1,50 @@ +import asyncio +from typing import cast +from urllib.parse import urljoin +from dotenv import load_dotenv +from os import getenv + +from src.common.db import connect_db +from src.common.hackernews import HackerNews +from src.common.utils import fetch_url + + +async def main(): + load_dotenv() + engine = connect_db( + mongodb_uri=cast(str, getenv("MONGODB_URI")), + database=cast(str, getenv("MONGODB_DATABASE")), + ) + HackerNews.set_engine(engine=engine) + await listen_updates() + + +async def listen_updates(batch_size=20): + while True: + response = await fetch_url(url=urljoin(HackerNews.base_url, "v0/updates.json")) + items = response["items"] + for i in range(0, len(items), batch_size): + batched_items = [ + items[item_i] + for item_i in range(i, i + batch_size) + if item_i < len(items) + ] + items_task = [ + asyncio.create_task(HackerNews.fetch_item(item_id)) + for item_id in batched_items + ] + items_responses = await asyncio.gather(*items_task) + story_items = [ + story_item + for story_item in items_responses + if story_item is not None and story_item["type"] == "story" + ] + items_instances = await HackerNews.save_items(items=story_items) + await HackerNews.fetch_comments(items=items_instances) + print( + f"Saved updated stories: {[story_item.id for story_item in items_instances]}" + ) + await asyncio.sleep(60) + + +asyncio.run(main()) From 6c64e14fcb2cfa8a23a11fdd75efcd5412e049ae Mon Sep 17 00:00:00 2001 From: ginruh Date: Sat, 15 Jun 2024 21:57:31 +0530 Subject: [PATCH 04/18] feat: listener as a separate module --- docker-compose.dev.yml | 27 +++++++++++++++++++++++++++ docker-compose.yml | 2 +- pyproject.toml | 2 +- src/__init__.py | 27 --------------------------- Dockerfile => src/listener/Dockerfile | 2 +- src/{updates => listener}/__init__.py | 0 6 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 docker-compose.dev.yml rename Dockerfile => src/listener/Dockerfile (94%) rename src/{updates => listener}/__init__.py (100%) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..1b45484 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,27 @@ +x-healthcheck-config: &healthcheck-config + interval: 10s + timeout: 10s + retries: 5 + +services: + listener: + build: + context: . + dockerfile: src/listener/Dockerfile + depends_on: + - mongodb + + mongodb: + image: mongo:7.0.11 + ports: + - "27017:27017" + env_file: + - .env + healthcheck: + <<: *healthcheck-config + test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet + volumes: + - mongodb_data:/data/db + +volumes: + mongodb_data: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 24db08d..77f30b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ services: - app: + listener: build: context: . dockerfile: Dockerfile diff --git a/pyproject.toml b/pyproject.toml index 425d2fa..7e6e35f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,4 +20,4 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -app = "src:main" \ No newline at end of file +listener = "src:listener.main" diff --git a/src/__init__.py b/src/__init__.py index 844b817..e69de29 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,27 +0,0 @@ -import asyncio -from typing import cast -from dotenv import load_dotenv -from os import getenv -from src.db import connect_db -from src.hackernews import HackerNews - - -async def main(): - load_dotenv() - engine = connect_db( - mongodb_uri=cast(str, getenv("MONGODB_URI")), - database=cast(str, getenv("MONGODB_DATABASE")), - ) - HackerNews.set_engine(engine=engine) - latest_item_id = await HackerNews.get_latest_item() - print(f"Latest item ID: {latest_item_id}") - if latest_item_id is None: - raise Exception("Unable to fetch latest item_id. Exiting") - tasks = [ - # asyncio.create_task(HackerNews.fetch_story_items(end_item=latest_item_id)), - asyncio.create_task(HackerNews.listen_updates()), - ] - await asyncio.gather(*tasks) - - -asyncio.run(main()) diff --git a/Dockerfile b/src/listener/Dockerfile similarity index 94% rename from Dockerfile rename to src/listener/Dockerfile index e3c8517..e41a83d 100644 --- a/Dockerfile +++ b/src/listener/Dockerfile @@ -19,4 +19,4 @@ RUN poetry install FROM base as prod WORKDIR /app COPY --from=base /app /app -CMD ["/app/.venv/bin/app"] \ No newline at end of file +CMD ["/app/.venv/bin/listener"] \ No newline at end of file diff --git a/src/updates/__init__.py b/src/listener/__init__.py similarity index 100% rename from src/updates/__init__.py rename to src/listener/__init__.py From 589523dfabfa9cd766626529824571ecaf38fab5 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 16 Jun 2024 12:08:28 +0530 Subject: [PATCH 05/18] feat: added worflow file --- .github/workflows/master.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/master.yml diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 0000000..5a612b1 --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,35 @@ +name: Create and publish images + +on: + push: + branches: + - master + +jobs: + publish: + runs-on: ubuntu-latest + + permissions: + packages: write + contents: read + attestations: write + id-token: write + + steps: + - uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push listener image + id: push + uses: docker/build-push-action@v5 + with: + context: . + file: src/listener/Dockerfile + push: true + tags: ghcr.io/${{ github.repository }}/listener:latest From 25f13a9ac18642bb2523e83cafd95ab63dc99779 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 16 Jun 2024 12:15:07 +0530 Subject: [PATCH 06/18] chore: updated docker-compose file --- docker-compose.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 77f30b0..c778570 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,9 @@ services: listener: - build: - context: . - dockerfile: Dockerfile + image: ghcr.io/ginruh/hckernews/listener:latest + + watchtower: + image: containrrr/watchtower + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: --interval 30 From ce4589bb08350964485be974e714db11784e5ad6 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 16 Jun 2024 12:49:30 +0530 Subject: [PATCH 07/18] feat: updated workflow --- .github/workflows/master.yml | 17 +++++++++++++++++ docker-compose.yml | 6 ------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 5a612b1..7f57e2f 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -33,3 +33,20 @@ jobs: file: src/listener/Dockerfile push: true tags: ghcr.io/${{ github.repository }}/listener:latest + + - name: Setup ssh key + run: | + mkdir -p ~/.ssh/ + echo "${{ secrets.ACTION_PRIVATE_KEY }}" > ~/.ssh/github_action.key + sudo chmod 600 ~/.ssh/github_action.key + ssh-keyscan -H ${{ secrets.SERVER_IP }} > ~/.ssh/known_hosts + + - name: Deploy + run: | + ssh {{ secrets.SERVER_USERNAME }}@{{ secrets.SERVER_IP }} /bin/bash << EOF + cd {{ secrets.REPO_PATH }}; + git pull origin master; + docker compose down; + docker compose pull; + docker compose up -d; + EOF diff --git a/docker-compose.yml b/docker-compose.yml index c778570..8835016 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,3 @@ services: listener: image: ghcr.io/ginruh/hckernews/listener:latest - - watchtower: - image: containrrr/watchtower - volumes: - - /var/run/docker.sock:/var/run/docker.sock - command: --interval 30 From db223e4c0d05ab54420149cfaa2aa835d8005fe8 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 16 Jun 2024 12:53:45 +0530 Subject: [PATCH 08/18] fix: workflow file --- .github/workflows/master.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 7f57e2f..7e072bd 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -43,8 +43,8 @@ jobs: - name: Deploy run: | - ssh {{ secrets.SERVER_USERNAME }}@{{ secrets.SERVER_IP }} /bin/bash << EOF - cd {{ secrets.REPO_PATH }}; + ssh ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_IP }} /bin/bash << EOF + cd ${{ secrets.REPO_PATH }}; git pull origin master; docker compose down; docker compose pull; From 8ddbc97b5e37277848acd3d7cf2cfb36ae29559a Mon Sep 17 00:00:00 2001 From: Akshay Date: Sun, 16 Jun 2024 12:56:32 +0530 Subject: [PATCH 09/18] fix: added ssh key path in workflow --- .github/workflows/master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 7e072bd..7b567c6 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -43,7 +43,7 @@ jobs: - name: Deploy run: | - ssh ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_IP }} /bin/bash << EOF + ssh ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_IP }} -i ~/.ssh/github_action.key /bin/bash << EOF cd ${{ secrets.REPO_PATH }}; git pull origin master; docker compose down; From 754bda69174ea547b795daa2e9e4e09dbb2948ef Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 16 Jun 2024 13:04:06 +0530 Subject: [PATCH 10/18] fix: create .env file in workflow --- .github/workflows/master.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 7b567c6..6601e7c 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -25,6 +25,11 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Create .env file + run: | + echo "MONGODB_URI=${{ secrets.APP_MONGODB_URI }}" > .env + echo "MONGODB_DATABASE=${{ secrets.APP_MONGODB_DATABASE }}" >> .env + - name: Build and push listener image id: push uses: docker/build-push-action@v5 From ee25ffce698b27fb99fad75a3394fe8616a8755f Mon Sep 17 00:00:00 2001 From: ginruh Date: Mon, 17 Jun 2024 00:08:45 +0530 Subject: [PATCH 11/18] feat: added cron for top stories --- docker-compose.dev.yml | 7 +++++++ pyproject.toml | 1 + src/common/hackernews.py | 4 ++++ src/listener/Dockerfile | 4 +++- src/top_stories/Dockerfile | 32 ++++++++++++++++++++++++++++++++ src/top_stories/__init__.py | 26 ++++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/top_stories/Dockerfile create mode 100644 src/top_stories/__init__.py diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 1b45484..98a4414 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -10,6 +10,13 @@ services: dockerfile: src/listener/Dockerfile depends_on: - mongodb + + top_stories: + build: + context: . + dockerfile: src/top_stories/Dockerfile + depends_on: + - mongodb mongodb: image: mongo:7.0.11 diff --git a/pyproject.toml b/pyproject.toml index 7e6e35f..e27652c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,3 +21,4 @@ build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] listener = "src:listener.main" +top_stories = "src:top_stories.main" diff --git a/src/common/hackernews.py b/src/common/hackernews.py index f455194..1ce6526 100644 --- a/src/common/hackernews.py +++ b/src/common/hackernews.py @@ -79,6 +79,10 @@ async def fetch_comments(cls, *, items: list[Item], batch_size=20000): comment_items = await save_items(engine=cls.engine, items=comment_responses) await cls.fetch_comments(items=comment_items) + @classmethod + async def fetch_top_stories(cls) -> list[int]: + return await fetch_url(urljoin(cls.base_url, f"v0/topstories.json")) + @classmethod async def save_items(cls, items: list[dict | None]): return await save_items(engine=HackerNews.engine, items=items) diff --git a/src/listener/Dockerfile b/src/listener/Dockerfile index e41a83d..255c78b 100644 --- a/src/listener/Dockerfile +++ b/src/listener/Dockerfile @@ -8,6 +8,8 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ POETRY_VIRTUALENVS_CREATE=1 \ POETRY_CACHE_DIR=/tmp/poetry_cache ENV PATH="${PATH}:/home/py/.local/bin" + +FROM base as build RUN pip install --user poetry WORKDIR /app COPY pyproject.toml poetry.lock ./ @@ -18,5 +20,5 @@ RUN poetry install FROM base as prod WORKDIR /app -COPY --from=base /app /app +COPY --from=build /app /app CMD ["/app/.venv/bin/listener"] \ No newline at end of file diff --git a/src/top_stories/Dockerfile b/src/top_stories/Dockerfile new file mode 100644 index 0000000..114cb12 --- /dev/null +++ b/src/top_stories/Dockerfile @@ -0,0 +1,32 @@ +FROM python:3.12.3-slim as base +RUN useradd --create-home py +USER py +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + POETRY_NO_INTERACTION=1 \ + POETRY_VIRTUALENVS_IN_PROJECT=1 \ + POETRY_VIRTUALENVS_CREATE=1 \ + POETRY_CACHE_DIR=/tmp/poetry_cache +ENV PATH="${PATH}:/home/py/.local/bin" + +FROM base as build +RUN pip install --user poetry +WORKDIR /app +COPY pyproject.toml poetry.lock ./ +RUN poetry install --no-root +COPY . . +RUN poetry install + +FROM base as prod +USER root +RUN apt update -y +RUN apt install cron -y +RUN echo "* * * * * root /app/.venv/bin/top_stories > /proc/1/fd/1 2>&1\n" >> /etc/cron.d/topstories +RUN chmod 0644 /etc/cron.d/topstories + +USER py +WORKDIR /app +COPY --from=build /app /app + +USER root +CMD cron -f -l 2 \ No newline at end of file diff --git a/src/top_stories/__init__.py b/src/top_stories/__init__.py new file mode 100644 index 0000000..80f4408 --- /dev/null +++ b/src/top_stories/__init__.py @@ -0,0 +1,26 @@ +import asyncio +from typing import cast +from dotenv import load_dotenv +from os import getenv + +from src.common.db import connect_db +from src.common.hackernews import HackerNews + + +def main(): + asyncio.run(run()) + + +async def run(): + load_dotenv() + engine = connect_db( + mongodb_uri=cast(str, getenv("MONGODB_URI")), + database=cast(str, getenv("MONGODB_DATABASE")), + ) + HackerNews.set_engine(engine=engine) + await fetch_top_stories() + + +async def fetch_top_stories(): + top_stories = await HackerNews.fetch_top_stories() + print(top_stories) From eee536970df2aae2ba34f8a20fae35ecf2f30760 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sat, 22 Jun 2024 18:54:50 +0530 Subject: [PATCH 12/18] feat: switched from mongo to pg --- docker-compose.dev.yml | 16 +- poetry.lock | 465 ++++++++++++++++-------------------- pyproject.toml | 3 +- src/common/db.py | 130 ++++++---- src/common/hackernews.py | 37 ++- src/listener/__init__.py | 16 +- src/top_stories/__init__.py | 25 +- 7 files changed, 357 insertions(+), 335 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 98a4414..9d19869 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -10,6 +10,7 @@ services: dockerfile: src/listener/Dockerfile depends_on: - mongodb + - postgres top_stories: build: @@ -17,18 +18,19 @@ services: dockerfile: src/top_stories/Dockerfile depends_on: - mongodb + - postgres - mongodb: - image: mongo:7.0.11 + postgres: + image: postgres:16.3-alpine ports: - - "27017:27017" + - "5432:5432" env_file: - .env healthcheck: <<: *healthcheck-config - test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet volumes: - - mongodb_data:/data/db - + - pg_data:/var/lib/postgresql/data + volumes: - mongodb_data: \ No newline at end of file + mongodb_data: + pg_data: diff --git a/poetry.lock b/poetry.lock index 44e1797..24f3ca5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -110,16 +110,59 @@ files = [ frozenlist = ">=1.1.0" [[package]] -name = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" +name = "asyncpg" +version = "0.29.0" +description = "An asyncio PostgreSQL driver" optional = false -python-versions = ">=3.8" +python-versions = ">=3.8.0" files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, + {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, + {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, + {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, + {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, + {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, + {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, + {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, + {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, + {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, + {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, + {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, + {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, + {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, + {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, + {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, + {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, + {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, + {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, + {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, + {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, + {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, + {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, + {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, + {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, + {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, + {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, ] +[package.extras] +docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] + [[package]] name = "attrs" version = "23.2.0" @@ -139,26 +182,6 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] -[[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - [[package]] name = "frozenlist" version = "1.4.1" @@ -245,6 +268,77 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + [[package]] name = "idna" version = "3.7" @@ -256,30 +350,6 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] -[[package]] -name = "motor" -version = "3.4.0" -description = "Non-blocking MongoDB driver for Tornado or asyncio" -optional = false -python-versions = ">=3.7" -files = [ - {file = "motor-3.4.0-py3-none-any.whl", hash = "sha256:4b1e1a0cc5116ff73be2c080a72da078f2bb719b53bc7a6bb9e9a2f7dcd421ed"}, - {file = "motor-3.4.0.tar.gz", hash = "sha256:c89b4e4eb2e711345e91c7c9b122cb68cce0e5e869ed0387dd0acb10775e3131"}, -] - -[package.dependencies] -pymongo = ">=4.5,<5" - -[package.extras] -aws = ["pymongo[aws] (>=4.5,<5)"] -encryption = ["pymongo[encryption] (>=4.5,<5)"] -gssapi = ["pymongo[gssapi] (>=4.5,<5)"] -ocsp = ["pymongo[ocsp] (>=4.5,<5)"] -snappy = ["pymongo[snappy] (>=4.5,<5)"] -srv = ["pymongo[srv] (>=4.5,<5)"] -test = ["aiohttp (!=3.8.6)", "mockupdb", "motor[encryption]", "pytest (>=7)", "tornado (>=5)"] -zstd = ["pymongo[zstd] (>=4.5,<5)"] - [[package]] name = "multidict" version = "6.0.5" @@ -380,232 +450,105 @@ files = [ ] [[package]] -name = "odmantic" -version = "1.0.2" -description = "ODMantic, an AsyncIO MongoDB Object Document Mapper for Python using type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "odmantic-1.0.2-py3-none-any.whl", hash = "sha256:0475c763687624b70ba4d52266a7d0bdfb74dda7ec40162669334e9d41c06049"}, - {file = "odmantic-1.0.2.tar.gz", hash = "sha256:bbd0d46bc920e0e6d966d9f3add7396bc92377d2c73860d071d5ca8102f57235"}, -] - -[package.dependencies] -motor = ">=3.1.1" -pydantic = ">=2.5.2" -pymongo = ">=4.1.0" - -[package.extras] -dev = ["ipython (>=7.16.1,<7.17.0)", "semver (>=2.13.0,<2.14.0)", "typer (>=0.4.1,<0.5.0)"] -doc = ["mkdocs-macros-plugin (>=1.0.4,<1.1.0)", "mkdocs-material (>=9.5.2,<9.6.0)", "mkdocstrings[python] (>=0.24.0,<0.25.0)", "pydocstyle[toml] (>=6.3.0,<6.4.0)"] -fastapi = ["fastapi (>=0.100.0)"] -lint = ["mypy (>=1.4.1,<1.5.0)", "ruff (>=0.3.3,<0.4.0)"] -test = ["async-asgi-testclient (>=1.4.11,<1.5.0)", "asyncmock (>=0.4.2,<0.5.0)", "coverage[toml] (>=6.2,<7.0)", "darglint (>=1.8.1,<1.9.0)", "fastapi (>=0.104.0)", "httpx (>=0.24.1,<0.25.0)", "inline-snapshot (>=0.6.0,<0.7.0)", "pytest (>=7.0,<8.0)", "pytest-asyncio (>=0.16.0,<0.17.0)", "pytest-benchmark (>=4.0.0,<4.1.0)", "pytest-codspeed (>=2.1.0,<2.2.0)", "pytest-sugar (>=0.9.5,<0.10.0)", "pytest-xdist (>=2.1.0,<2.2.0)", "pytz (>=2023.3,<2024.0)", "requests (>=2.24,<3.0)", "types-pytz (>=2023.3.0.0,<2023.4.0.0)", "uvicorn (>=0.17.0,<0.18.0)"] - -[[package]] -name = "pydantic" -version = "2.7.1" -description = "Data validation using Python type hints" +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, - {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.18.2" -typing-extensions = ">=4.6.1" - [package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.18.2" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, - {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, - {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, - {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, - {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, - {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, - {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, - {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, - {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, - {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, - {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, - {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, - {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, - {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, - {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, - {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, - {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, - {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, - {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, - {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, - {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, - {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, - {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, - {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, - {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, - {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, - {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, - {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, - {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, - {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, - {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, - {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, - {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +cli = ["click (>=5.0)"] [[package]] -name = "pymongo" -version = "4.7.2" -description = "Python driver for MongoDB " +name = "sqlalchemy" +version = "2.0.31" +description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "pymongo-4.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:268d8578c0500012140c5460755ea405cbfe541ef47c81efa9d6744f0f99aeca"}, - {file = "pymongo-4.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:827611beb6c483260d520cfa6a49662d980dfa5368a04296f65fa39e78fccea7"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a754e366c404d19ff3f077ddeed64be31e0bb515e04f502bf11987f1baa55a16"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44efab10d9a3db920530f7bcb26af8f408b7273d2f0214081d3891979726328"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35b3f0c7d49724859d4df5f0445818d525824a6cd55074c42573d9b50764df67"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e37faf298a37ffb3e0809e77fbbb0a32b6a2d18a83c59cfc2a7b794ea1136b0"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1bcd58669e56c08f1e72c5758868b5df169fe267501c949ee83c418e9df9155"}, - {file = "pymongo-4.7.2-cp310-cp310-win32.whl", hash = "sha256:c72d16fede22efe7cdd1f422e8da15760e9498024040429362886f946c10fe95"}, - {file = "pymongo-4.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:12d1fef77d25640cb78893d07ff7d2fac4c4461d8eec45bd3b9ad491a1115d6e"}, - {file = "pymongo-4.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc5af24fcf5fc6f7f40d65446400d45dd12bea933d0299dc9e90c5b22197f1e9"}, - {file = "pymongo-4.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:730778b6f0964b164c187289f906bbc84cb0524df285b7a85aa355bbec43eb21"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47a1a4832ef2f4346dcd1a10a36ade7367ad6905929ddb476459abb4fd1b98cb"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6eab12c6385526d386543d6823b07187fefba028f0da216506e00f0e1855119"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37e9ea81fa59ee9274457ed7d59b6c27f6f2a5fe8e26f184ecf58ea52a019cb8"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e9d9d2c0aae73aa4369bd373ac2ac59f02c46d4e56c4b6d6e250cfe85f76802"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6e00a79dff22c9a72212ad82021b54bdb3b85f38a85f4fc466bde581d7d17a"}, - {file = "pymongo-4.7.2-cp311-cp311-win32.whl", hash = "sha256:02efd1bb3397e24ef2af45923888b41a378ce00cb3a4259c5f4fc3c70497a22f"}, - {file = "pymongo-4.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:87bb453ac3eb44db95cb6d5a616fbc906c1c00661eec7f55696253a6245beb8a"}, - {file = "pymongo-4.7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:12c466e02133b7f8f4ff1045c6b5916215c5f7923bc83fd6e28e290cba18f9f6"}, - {file = "pymongo-4.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f91073049c43d14e66696970dd708d319b86ee57ef9af359294eee072abaac79"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87032f818bf5052ab742812c715eff896621385c43f8f97cdd37d15b5d394e95"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a87eef394039765679f75c6a47455a4030870341cb76eafc349c5944408c882"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d275596f840018858757561840767b39272ac96436fcb54f5cac6d245393fd97"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82102e353be13f1a6769660dd88115b1da382447672ba1c2662a0fbe3df1d861"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194065c9d445017b3c82fb85f89aa2055464a080bde604010dc8eb932a6b3c95"}, - {file = "pymongo-4.7.2-cp312-cp312-win32.whl", hash = "sha256:db4380d1e69fdad1044a4b8f3bb105200542c49a0dde93452d938ff9db1d6d29"}, - {file = "pymongo-4.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:fadc6e8db7707c861ebe25b13ad6aca19ea4d2c56bf04a26691f46c23dadf6e4"}, - {file = "pymongo-4.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2cb77d09bd012cb4b30636e7e38d00b5f9be5eb521c364bde66490c45ee6c4b4"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56bf8b706946952acdea0fe478f8e44f1ed101c4b87f046859e6c3abe6c0a9f4"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcf337d1b252405779d9c79978d6ca15eab3cdaa2f44c100a79221bddad97c8a"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ffd1519edbe311df73c74ec338de7d294af535b2748191c866ea3a7c484cd15"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d59776f435564159196d971aa89422ead878174aff8fe18e06d9a0bc6d648c"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:347c49cf7f0ba49ea87c1a5a1984187ecc5516b7c753f31938bf7b37462824fd"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:84bc00200c3cbb6c98a2bb964c9e8284b641e4a33cf10c802390552575ee21de"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fcaf8c911cb29316a02356f89dbc0e0dfcc6a712ace217b6b543805690d2aefd"}, - {file = "pymongo-4.7.2-cp37-cp37m-win32.whl", hash = "sha256:b48a5650ee5320d59f6d570bd99a8d5c58ac6f297a4e9090535f6561469ac32e"}, - {file = "pymongo-4.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5239ef7e749f1326ea7564428bf861d5250aa39d7f26d612741b1b1273227062"}, - {file = "pymongo-4.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2dcf608d35644e8d276d61bf40a93339d8d66a0e5f3e3f75b2c155a421a1b71"}, - {file = "pymongo-4.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25eeb2c18ede63891cbd617943dd9e6b9cbccc54f276e0b2e693a0cc40f243c5"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9349f0bb17a31371d4cacb64b306e4ca90413a3ad1fffe73ac7cd495570d94b5"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffd4d7cb2e6c6e100e2b39606d38a9ffc934e18593dc9bb326196afc7d93ce3d"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a8bd37f5dabc86efceb8d8cbff5969256523d42d08088f098753dba15f3b37a"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c78f156edc59b905c80c9003e022e1a764c54fd40ac4fea05b0764f829790e2"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d892fb91e81cccb83f507cdb2ea0aa026ec3ced7f12a1d60f6a5bf0f20f9c1f"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87832d6076c2c82f42870157414fd876facbb6554d2faf271ffe7f8f30ce7bed"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ce1a374ea0e49808e0380ffc64284c0ce0f12bd21042b4bef1af3eb7bdf49054"}, - {file = "pymongo-4.7.2-cp38-cp38-win32.whl", hash = "sha256:eb0642e5f0dd7e86bb358749cc278e70b911e617f519989d346f742dc9520dfb"}, - {file = "pymongo-4.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:4bdb5ffe1cd3728c9479671a067ef44dacafc3743741d4dc700c377c4231356f"}, - {file = "pymongo-4.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:743552033c63f0afdb56b9189ab04b5c1dbffd7310cf7156ab98eebcecf24621"}, - {file = "pymongo-4.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5239776633f7578b81207e5646245415a5a95f6ae5ef5dff8e7c2357e6264bfc"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727ad07952c155cd20045f2ce91143c7dc4fb01a5b4e8012905a89a7da554b0c"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9385654f01a90f73827af4db90c290a1519f7d9102ba43286e187b373e9a78e9"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d833651f1ba938bb7501f13e326b96cfbb7d98867b2d545ca6d69c7664903e0"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf17ea9cea14d59b0527403dd7106362917ced7c4ec936c4ba22bd36c912c8e0"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cecd2df037249d1c74f0af86fb5b766104a5012becac6ff63d85d1de53ba8b98"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65b4c00dedbd333698b83cd2095a639a6f0d7c4e2a617988f6c65fb46711f028"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9b6cbc037108ff1a0a867e7670d8513c37f9bcd9ee3d2464411bfabf70ca002"}, - {file = "pymongo-4.7.2-cp39-cp39-win32.whl", hash = "sha256:cf28430ec1924af1bffed37b69a812339084697fd3f3e781074a0148e6475803"}, - {file = "pymongo-4.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:e004527ea42a6b99a8b8d5b42b42762c3bdf80f88fbdb5c3a9d47f3808495b86"}, - {file = "pymongo-4.7.2.tar.gz", hash = "sha256:9024e1661c6e40acf468177bf90ce924d1bc681d2b244adda3ed7b2f4c4d17d7"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, + {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, + {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, ] [package.dependencies] -dnspython = ">=1.16.0,<3.0.0" +greenlet = {version = "!=0.4.17", optional = true, markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""} +typing-extensions = ">=4.6.0" [package.extras] -aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] -encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] -gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] -ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] -snappy = ["python-snappy"] -test = ["pytest (>=7)"] -zstd = ["zstandard"] - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] [[package]] name = "typing-extensions" @@ -724,4 +667,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "3d297d8afc7eb3b5a7935738eefada67b60625a85942523cf1807a78c8cbd041" +content-hash = "648725a0bb874cd96c138cd8b77f070a4fff2fe6dfa8fede09d66c275ea5e4a8" diff --git a/pyproject.toml b/pyproject.toml index e27652c..e321678 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,9 +12,10 @@ packages = [ python = "^3.12" -odmantic = "^1.0.2" aiohttp = "^3.9.5" python-dotenv = "^1.0.1" +sqlalchemy = {extras = ["asyncio"], version = "^2.0.31"} +asyncpg = "^0.29.0" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/src/common/db.py b/src/common/db.py index ddf6dd9..afba762 100644 --- a/src/common/db.py +++ b/src/common/db.py @@ -1,30 +1,53 @@ +from datetime import datetime from typing import Optional -from odmantic import AIOEngine, Field, Model -from motor.motor_asyncio import AsyncIOMotorClient +from sqlalchemy import ForeignKey, insert, select, func +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession -class Item(Model): - id: int = Field(primary_field=True) - deleted: Optional[bool] = False - type: str - by: Optional[str] = None - time: int - dead: Optional[bool] = False - parent: Optional[int] - kids: list[int] - url: Optional[str] = None - score: Optional[int] = None - title: Optional[str] = None # null for comment - text: Optional[str] = None # maybe null for story - descendants: Optional[int] = 0 +class Base(DeclarativeBase): + pass -def connect_db(mongodb_uri: str, database: str) -> AIOEngine: - client = AsyncIOMotorClient(mongodb_uri) - return AIOEngine(client=client, database=database) +class Item(Base): + __tablename__ = "item" + id: Mapped[int] = mapped_column(primary_field=True) + deleted: Mapped[bool] = mapped_column(default=False) + type: Mapped[str] + by: Mapped[Optional[str]] = mapped_column(nullable=True) + time: Mapped[int] + dead: Mapped[bool] = mapped_column(default=False) + parent: Mapped[Optional[int]] = mapped_column(ForeignKey("item.id"), nullable=True) + kids: Mapped[list[int]] = mapped_column(default=[]) + url: Mapped[Optional[str]] = mapped_column(nullable=True) + score: Mapped[Optional[int]] = mapped_column(nullable=True) + title: Mapped[Optional[str]] = mapped_column(nullable=True) # null for comment + text: Mapped[Optional[str]] = mapped_column(nullable=True) # maybe null for story + descendants: Mapped[Optional[int]] = mapped_column(default=0) -async def save_items(*, engine: AIOEngine, items: list[dict | None]) -> list[Item]: + +class TopStories(Base): + __tablename__ = "top_stories" + + story_item_id: Mapped[int] = mapped_column(primary_key=True) + created_at: Mapped[datetime] = mapped_column( + primary_key=True, server_default=func.now() + ) + + +async def connect_db(postgres_uri: str) -> async_sessionmaker[AsyncSession]: + engine = create_async_engine(postgres_uri) + + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + return async_sessionmaker(engine, expire_on_commit=False) + + +async def save_items( + *, async_session: async_sessionmaker[AsyncSession], items: list[dict | None] +) -> list[Item]: filtered_items: list[dict] = [] for item in items: # removing failed request items @@ -39,27 +62,50 @@ async def save_items(*, engine: AIOEngine, items: list[dict | None]) -> list[Ite filtered_items.append(item) if item.get("type") == "comment": filtered_items.append(item) - return await engine.save_all( - [ - Item( - id=f_item["id"], - type=f_item["type"], - time=f_item["time"], - by=f_item.get("by", None), - dead=f_item.get("dead", False), - deleted=f_item.get("deleted", False), - descendants=f_item.get("descendants", 0), - kids=f_item.get("kids", []), - parent=f_item.get("parent", None), - score=f_item.get("score", None), - text=f_item.get("text", None), - title=f_item.get("title", None), - url=f_item.get("url", None), - ) - for f_item in filtered_items - ] - ) + hn_items = [ + dict( + id=f_item["id"], + type=f_item["type"], + time=f_item["time"], + by=f_item.get("by", None), + dead=f_item.get("dead", False), + deleted=f_item.get("deleted", False), + descendants=f_item.get("descendants", 0), + kids=f_item.get("kids", []), + parent=f_item.get("parent", None), + score=f_item.get("score", None), + text=f_item.get("text", None), + title=f_item.get("title", None), + url=f_item.get("url", None), + ) + for f_item in filtered_items + ] + async with async_session() as session: + async with session.begin(): + result = await session.scalars(insert(Item).returning(Item), hn_items) + return list(result.all()) + + +async def get_item(*, async_session: async_sessionmaker[AsyncSession], item_id: int): + async with async_session() as session: + stmt = select(Item).where(Item.id == item_id) + items = await session.scalars(stmt) + return items.first() + + +async def save_top_stories( + *, async_session: async_sessionmaker[AsyncSession], stories: list[int] +): + top_stories = [TopStories(story_item_id=story_item_id) for story_item_id in stories] + async with async_session() as session: + async with session.begin(): + session.add_all(top_stories) -async def get_item(*, engine: AIOEngine, item_id: int): - return await engine.find_one(Item, Item.id == item_id) +async def bulk_find_items( + *, async_session: async_sessionmaker[AsyncSession], items: list[int] +): + async with async_session() as session: + async with session.begin(): + result = await session.scalars(select(Item).where(Item.id.in_(items))) + return list(result.all()) diff --git a/src/common/hackernews.py b/src/common/hackernews.py index 1ce6526..78bfeca 100644 --- a/src/common/hackernews.py +++ b/src/common/hackernews.py @@ -1,18 +1,19 @@ import asyncio from urllib.parse import urljoin -from odmantic import AIOEngine +from sqlalchemy import Engine from .db import Item, get_item, save_items from .utils import fetch_url +from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession class HackerNews: base_url: str = "https://hacker-news.firebaseio.com" - engine: AIOEngine + session: async_sessionmaker[AsyncSession] @classmethod - def set_engine(cls, engine: AIOEngine) -> None: - cls.engine = engine + def set_session(cls, session: async_sessionmaker[AsyncSession]) -> None: + cls.session = session @classmethod async def get_latest_item(cls) -> int | None: @@ -36,7 +37,9 @@ async def fetch_story_items( # check for items already saved batched_items = [] for new_item in [i + c for c in range(batch_size)]: - item_instance = await get_item(engine=cls.engine, item_id=new_item) + item_instance = await get_item( + async_session=cls.session, item_id=new_item + ) if item_instance is None: batched_items.append(new_item) # fetch items not saved @@ -52,11 +55,27 @@ async def fetch_story_items( if story_item is not None and story_item["type"] == "story" ] # save them - items_instances = await save_items(engine=cls.engine, items=story_items) + items_instances = await save_items( + async_session=cls.session, items=story_items + ) # fetch comments await cls.fetch_comments(items=items_instances) print(f"Saved stories: {[story_item.id for story_item in items_instances]}") + @classmethod + async def bulk_fetch_items(cls, *, item_ids: list[int], batch_size: int = 20): + start_item = 0 + end_item = len(item_ids) + fetched_items = [] + for i in range(start_item, end_item, batch_size): + batched_items = [i + c for c in range(batch_size)] + items_tasks = [ + asyncio.create_task(cls.fetch_item(item_ids[i])) for i in batched_items + ] + items = await asyncio.gather(*items_tasks) + fetched_items.extend(items) + return fetched_items + @classmethod async def fetch_comments(cls, *, items: list[Item], batch_size=20000): # doing sort of breadth traversal and batching them @@ -76,7 +95,9 @@ async def fetch_comments(cls, *, items: list[Item], batch_size=20000): for item_id in batched_comments ] comment_responses = await asyncio.gather(*comment_tasks) - comment_items = await save_items(engine=cls.engine, items=comment_responses) + comment_items = await save_items( + async_session=cls.session, items=comment_responses + ) await cls.fetch_comments(items=comment_items) @classmethod @@ -85,4 +106,4 @@ async def fetch_top_stories(cls) -> list[int]: @classmethod async def save_items(cls, items: list[dict | None]): - return await save_items(engine=HackerNews.engine, items=items) + return await save_items(async_session=cls.session, items=items) diff --git a/src/listener/__init__.py b/src/listener/__init__.py index a7cb32b..b2a452e 100644 --- a/src/listener/__init__.py +++ b/src/listener/__init__.py @@ -9,13 +9,16 @@ from src.common.utils import fetch_url -async def main(): +def main(): + asyncio.run(run()) + + +async def run(): load_dotenv() - engine = connect_db( - mongodb_uri=cast(str, getenv("MONGODB_URI")), - database=cast(str, getenv("MONGODB_DATABASE")), + session = await connect_db( + postgres_uri=cast(str, getenv("POSTGRES_URI")), ) - HackerNews.set_engine(engine=engine) + HackerNews.set_session(session=session) await listen_updates() @@ -45,6 +48,3 @@ async def listen_updates(batch_size=20): f"Saved updated stories: {[story_item.id for story_item in items_instances]}" ) await asyncio.sleep(60) - - -asyncio.run(main()) diff --git a/src/top_stories/__init__.py b/src/top_stories/__init__.py index 80f4408..854543c 100644 --- a/src/top_stories/__init__.py +++ b/src/top_stories/__init__.py @@ -3,7 +3,7 @@ from dotenv import load_dotenv from os import getenv -from src.common.db import connect_db +from src.common.db import bulk_find_items, connect_db, save_top_stories from src.common.hackernews import HackerNews @@ -13,14 +13,23 @@ def main(): async def run(): load_dotenv() - engine = connect_db( - mongodb_uri=cast(str, getenv("MONGODB_URI")), - database=cast(str, getenv("MONGODB_DATABASE")), + session = await connect_db( + postgres_uri=cast(str, getenv("POSTGRES_URI")), ) - HackerNews.set_engine(engine=engine) - await fetch_top_stories() + HackerNews.set_session(session=session) + await sync_top_stories() -async def fetch_top_stories(): +async def sync_top_stories(): top_stories = await HackerNews.fetch_top_stories() - print(top_stories) + top_story_items = await HackerNews.bulk_fetch_items(item_ids=top_stories) + story_items = [ + tsi for tsi in top_story_items if tsi is not None and tsi.get("type") == "story" + ] + filtered_items = await bulk_find_items( + async_session=HackerNews.session, items=[i.get("id") for i in story_items] + ) + await save_top_stories( + async_session=HackerNews.session, stories=[item.id for item in filtered_items] + ) + print("Top stories synced successfully") From e5244fbfb1ad9310689142ed2ffb497760fa72c4 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 23 Jun 2024 13:23:54 +0530 Subject: [PATCH 13/18] feat: ported and tested listener and top stories to postgres --- docker-compose.dev.yml | 4 +--- src/common/db.py | 33 +++++++++++++++++++++++++++++---- src/listener/__init__.py | 17 +++++++++++------ src/top_stories/Dockerfile | 3 ++- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9d19869..666787c 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -9,7 +9,6 @@ services: context: . dockerfile: src/listener/Dockerfile depends_on: - - mongodb - postgres top_stories: @@ -17,8 +16,8 @@ services: context: . dockerfile: src/top_stories/Dockerfile depends_on: - - mongodb - postgres + - listener postgres: image: postgres:16.3-alpine @@ -32,5 +31,4 @@ services: - pg_data:/var/lib/postgresql/data volumes: - mongodb_data: pg_data: diff --git a/src/common/db.py b/src/common/db.py index afba762..387e740 100644 --- a/src/common/db.py +++ b/src/common/db.py @@ -1,8 +1,9 @@ from datetime import datetime from typing import Optional -from sqlalchemy import ForeignKey, insert, select, func +from sqlalchemy import ForeignKey, select, func, ARRAY, Integer from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession +from sqlalchemy.dialects.postgresql import Insert class Base(DeclarativeBase): @@ -12,14 +13,14 @@ class Base(DeclarativeBase): class Item(Base): __tablename__ = "item" - id: Mapped[int] = mapped_column(primary_field=True) + id: Mapped[int] = mapped_column(primary_key=True) deleted: Mapped[bool] = mapped_column(default=False) type: Mapped[str] by: Mapped[Optional[str]] = mapped_column(nullable=True) time: Mapped[int] dead: Mapped[bool] = mapped_column(default=False) parent: Mapped[Optional[int]] = mapped_column(ForeignKey("item.id"), nullable=True) - kids: Mapped[list[int]] = mapped_column(default=[]) + kids: Mapped[list[int]] = mapped_column(ARRAY(Integer), default=[]) url: Mapped[Optional[str]] = mapped_column(nullable=True) score: Mapped[Optional[int]] = mapped_column(nullable=True) title: Mapped[Optional[str]] = mapped_column(nullable=True) # null for comment @@ -48,11 +49,16 @@ async def connect_db(postgres_uri: str) -> async_sessionmaker[AsyncSession]: async def save_items( *, async_session: async_sessionmaker[AsyncSession], items: list[dict | None] ) -> list[Item]: + if len(items) == 0: + return [] filtered_items: list[dict] = [] for item in items: # removing failed request items if item is None: continue + # remove item with no type + if item.get("type") is None: + continue # not interested in jobs and polls if item.get("type") != "story" and item.get("type") != "comment": continue @@ -82,7 +88,26 @@ async def save_items( ] async with async_session() as session: async with session.begin(): - result = await session.scalars(insert(Item).returning(Item), hn_items) + stmt = Insert(Item).values(hn_items) + result = await session.scalars( + stmt.on_conflict_do_update( + index_elements=["id"], + set_={ + "type": stmt.excluded.type, + "time": stmt.excluded.time, + "by": stmt.excluded.by, + "dead": stmt.excluded.dead, + "deleted": stmt.excluded.deleted, + "descendants": stmt.excluded.descendants, + "kids": stmt.excluded.kids, + "parent": stmt.excluded.parent, + "score": stmt.excluded.score, + "text": stmt.excluded.text, + "title": stmt.excluded.title, + "url": stmt.excluded.url, + }, + ).returning(Item) + ) return list(result.all()) diff --git a/src/listener/__init__.py b/src/listener/__init__.py index b2a452e..e2d1b2b 100644 --- a/src/listener/__init__.py +++ b/src/listener/__init__.py @@ -1,4 +1,5 @@ import asyncio + from typing import cast from urllib.parse import urljoin from dotenv import load_dotenv @@ -40,11 +41,15 @@ async def listen_updates(batch_size=20): story_items = [ story_item for story_item in items_responses - if story_item is not None and story_item["type"] == "story" + if story_item is not None and story_item.get("type") == "story" ] - items_instances = await HackerNews.save_items(items=story_items) - await HackerNews.fetch_comments(items=items_instances) - print( - f"Saved updated stories: {[story_item.id for story_item in items_instances]}" - ) + try: + items_instances = await HackerNews.save_items(items=story_items) + await HackerNews.fetch_comments(items=items_instances) + print( + f"Saved updated stories: {[story_item.id for story_item in items_instances]}" + ) + except Exception as e: + print(e) + await asyncio.sleep(60) diff --git a/src/top_stories/Dockerfile b/src/top_stories/Dockerfile index 114cb12..4f166b7 100644 --- a/src/top_stories/Dockerfile +++ b/src/top_stories/Dockerfile @@ -21,7 +21,8 @@ FROM base as prod USER root RUN apt update -y RUN apt install cron -y -RUN echo "* * * * * root /app/.venv/bin/top_stories > /proc/1/fd/1 2>&1\n" >> /etc/cron.d/topstories +# run at 00:00, 06:00, 12:00, 18:00 +RUN echo "0 0,6,12,18 * * * root /app/.venv/bin/top_stories > /proc/1/fd/1 2>&1\n" >> /etc/cron.d/topstories RUN chmod 0644 /etc/cron.d/topstories USER py From a86acc5f63fb67c4825c22e91485750db183840e Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 23 Jun 2024 13:40:22 +0530 Subject: [PATCH 14/18] fix: updated .env --- .github/workflows/master.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 6601e7c..1182cde 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -27,8 +27,7 @@ jobs: - name: Create .env file run: | - echo "MONGODB_URI=${{ secrets.APP_MONGODB_URI }}" > .env - echo "MONGODB_DATABASE=${{ secrets.APP_MONGODB_DATABASE }}" >> .env + echo "POSTGRES_URI=${{ secrets.APP_POSTGRES_URI }}" > .env - name: Build and push listener image id: push From 0271abf24e833caa1fe4bad4c5d4d8c306fcb9ff Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 23 Jun 2024 13:47:24 +0530 Subject: [PATCH 15/18] feat: added top_stories container and updated workflow --- .github/workflows/master.yml | 9 +++++++++ docker-compose.yml | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 1182cde..df9a8d6 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -38,6 +38,15 @@ jobs: push: true tags: ghcr.io/${{ github.repository }}/listener:latest + - name: Build and push top_stories image + id: push + uses: docker/build-push-action@v5 + with: + context: . + file: src/top_stories/Dockerfile + push: true + tags: ghcr.io/${{ github.repository }}/top_stories:latest + - name: Setup ssh key run: | mkdir -p ~/.ssh/ diff --git a/docker-compose.yml b/docker-compose.yml index 8835016..d85e22f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,7 @@ services: listener: image: ghcr.io/ginruh/hckernews/listener:latest + + top_stories: + image: ghcr.io/ginruh/hckernews/top_stories:latest + depends_on: listener From 8d5949f3845c8e8138adb2f7b03afe28b8d8fec9 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 23 Jun 2024 13:48:47 +0530 Subject: [PATCH 16/18] fix: removed id: push --- .github/workflows/master.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index df9a8d6..dc57dea 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -30,7 +30,6 @@ jobs: echo "POSTGRES_URI=${{ secrets.APP_POSTGRES_URI }}" > .env - name: Build and push listener image - id: push uses: docker/build-push-action@v5 with: context: . @@ -39,7 +38,6 @@ jobs: tags: ghcr.io/${{ github.repository }}/listener:latest - name: Build and push top_stories image - id: push uses: docker/build-push-action@v5 with: context: . From 6f71dd89e846d6a228ddcaabedead48955a650fb Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 23 Jun 2024 13:50:34 +0530 Subject: [PATCH 17/18] fix: fixed depends_on --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d85e22f..94a0f7f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,4 +4,5 @@ services: top_stories: image: ghcr.io/ginruh/hckernews/top_stories:latest - depends_on: listener + depends_on: + - listener From 451048c43877049b304df90b5e6339abb2466f16 Mon Sep 17 00:00:00 2001 From: ginruh Date: Sun, 23 Jun 2024 13:52:27 +0530 Subject: [PATCH 18/18] fix: fixed depends_on --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 94a0f7f..147e325 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,5 +4,5 @@ services: top_stories: image: ghcr.io/ginruh/hckernews/top_stories:latest - depends_on: - - listener + depends_on: + - listener