Skip to content

Commit

Permalink
Add wrapper class for PyPI interaction (#18)
Browse files Browse the repository at this point in the history
* Add wrapper class for PyPI interaction

Signed-off-by: GitHub <[email protected]>

* Add endpoint to call the echo endpoint

Signed-off-by: GitHub <[email protected]>

* Remove duplicate route path

It's already present in the base URL

Signed-off-by: GitHub <[email protected]>

---------

Signed-off-by: GitHub <[email protected]>
  • Loading branch information
shenanigansd authored Mar 7, 2024
1 parent 9c1bbcd commit 25c481d
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 55 deletions.
13 changes: 9 additions & 4 deletions src/reporter/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
import sentry_sdk

from reporter.constants import GIT_SHA, Sentry
from reporter.http_client import HTTPClientDependency
from reporter.pypi_client import PyPIClientDependency
from reporter.models import Observation, ServerMetadata
from reporter.observations import send_observation

from reporter.dependencies import build_graph_client
from reporter.mailer import build_report_email_content, send_mail
Expand All @@ -33,9 +32,15 @@ async def metadata() -> ServerMetadata:
)


@app.get("/echo", summary="Echo the username of the PyPI User")
async def echo(pypi_client: PyPIClientDependency) -> str:
"""Return the username of the PyPI User."""
return await pypi_client.echo()


@app.post("/report/{project_name}")
async def report_endpoint(project_name: str, observation: Observation, http_client: HTTPClientDependency):
await send_observation(project_name=project_name, observation=observation, http_client=http_client)
async def report_endpoint(project_name: str, observation: Observation, pypi_client: PyPIClientDependency):
await pypi_client.send_observation(project_name=project_name, observation=observation)


@app.post("/report/email")
Expand Down
28 changes: 0 additions & 28 deletions src/reporter/http_client.py

This file was deleted.

13 changes: 0 additions & 13 deletions src/reporter/observations.py

This file was deleted.

45 changes: 45 additions & 0 deletions src/reporter/pypi_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from collections.abc import Generator
from functools import cache
from typing import Annotated

import httpx
from fastapi import Depends
from fastapi.encoders import jsonable_encoder

from reporter.constants import PyPI
from reporter.models import Observation


class BearerAuthentication(httpx.Auth):
def __init__(self, *, token: str) -> None:
self.token = token

def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response, None]:
request.headers["Authorization"] = f"Bearer {self.token}"
yield request


class PyPIClient:
"""PyPI client to interact with the PyPI API."""

def __init__(self) -> None:
auth = BearerAuthentication(token=PyPI.api_token)
self.http_client = httpx.AsyncClient(auth=auth, base_url=PyPI.base_url)

async def echo(self) -> str:
response = await self.http_client.get("/echo")
return response.text

async def send_observation(self, project_name: str, observation: Observation) -> None:
path = f"/projects/{project_name}/observations"
json = jsonable_encoder(observation)

await self.http_client.post(path, json=json)


@cache
def get_pypi_client() -> PyPIClient:
return PyPIClient()


PyPIClientDependency = Annotated[PyPIClient, Depends(get_pypi_client)]
12 changes: 7 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
import pytest

from reporter.app import app
from reporter.http_client import http_client
from reporter.pypi_client import PyPIClient, get_pypi_client


@pytest.fixture(scope="session")
def mock_http_client() -> MagicMock:
return MagicMock(spec=httpx.AsyncClient)
def mock_pypi_client() -> PyPIClient:
client = PyPIClient()
client.http_client = MagicMock(spec=httpx.AsyncClient)
return client


@pytest.fixture(scope="session", autouse=True)
def _override_dependencies( # type: ignore
mock_http_client: MagicMock,
mock_pypi_client: PyPIClient,
):
app.dependency_overrides[http_client] = lambda: mock_http_client
app.dependency_overrides[get_pypi_client] = lambda: mock_pypi_client
8 changes: 3 additions & 5 deletions tests/test_report.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from unittest.mock import MagicMock

from fastapi.testclient import TestClient

from reporter.pypi_client import PyPIClient
from reporter.app import app

test_client = TestClient(app)


def test_report(mock_http_client: MagicMock):
def test_report(mock_pypi_client: PyPIClient):
project_name = "remmy"
json = {
"kind": "is_malware",
Expand All @@ -17,7 +15,7 @@ def test_report(mock_http_client: MagicMock):
}
test_client.post(f"/report/{project_name}", json=json)

mock_http_client.post.assert_called_with("/danger-api/projects/remmy/observations", json=json) # type: ignore
mock_pypi_client.http_client.post.assert_called_with("/projects/remmy/observations", json=json) # type: ignore


def test_invalid_report_payload():
Expand Down

0 comments on commit 25c481d

Please sign in to comment.