Skip to content

Commit

Permalink
refactor: Make calls to datasource async
Browse files Browse the repository at this point in the history
  • Loading branch information
PurpleBooth committed Jul 4, 2023
1 parent 5bf68de commit 675e723
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 18 deletions.
20 changes: 19 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ruff = "^0.0.276"
mypy = "^1.4.0"
pytest = "^7.3.2"
wiremock = "^2.5.0"
pytest-asyncio = "^0.21.0"

[build-system]
requires = ["poetry-core"]
Expand Down
Empty file added src/cli/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions src/cli/async_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import asyncio
from typing import Callable, Awaitable, ParamSpec, TypeVar

P = ParamSpec("P")
R = TypeVar("R")


def async_to_sync(f: Callable[P, Awaitable[R]]) -> Callable[P, R]:
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
return asyncio.run(f(*args, **kwargs))

return inner
5 changes: 5 additions & 0 deletions src/cli/parsers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from httpx import URL


def parse_url(url: str) -> URL:
return URL(url)
21 changes: 13 additions & 8 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

from httpx import URL

from src.cli.async_helper import async_to_sync
from src.cli.parsers import parse_url

UK_CARBON_INTENSITY_API_BASE_URL: URL = URL("https://api.carbonintensity.org.uk")
FILE_FOR_INTENSITY_READING: str = ".carbon_intensity"

Expand All @@ -21,10 +24,6 @@ class DataSource(StrEnum):
UK_CARBON_INTENSITY = "uk-carbon-intensity"


def parse_url(url: str) -> URL:
return URL(url)


def main(
max_carbon_intensity: Annotated[
int,
Expand Down Expand Up @@ -57,22 +56,28 @@ def main(
),
] = UK_CARBON_INTENSITY_API_BASE_URL,
) -> None:
carbon_intensity(data_source, from_file_carbon_intensity_file_path, max_carbon_intensity,
uk_carbon_intensity_api_base_url)


@async_to_sync
async def carbon_intensity(
data_source,
from_file_carbon_intensity_file_path,
max_carbon_intensity, uk_carbon_intensity_api_base_url):
intensity_repo: CarbonIntensityRepo = FromFileCarbonIntensityRepo(
from_file_carbon_intensity_file_path
)

match data_source:
case DataSource.FILE:
intensity_repo = intensity_repo
case DataSource.UK_CARBON_INTENSITY:
intensity_repo = UkCarbonIntensityApiRepo(
base_url=uk_carbon_intensity_api_base_url
)

if intensity_repo.get_carbon_intensity() > max_carbon_intensity:
if await intensity_repo.get_carbon_intensity() > max_carbon_intensity:
typer.echo("Carbon levels exceed threshold, skipping.")
raise typer.Exit(1)

typer.echo("Carbon levels under threshold, proceeding.")


Expand Down
14 changes: 7 additions & 7 deletions src/repos/carbon_intensity.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
from pathlib import Path
from typing import Protocol

from httpx import URL, Client
from httpx import URL, Client, AsyncClient
from pydantic import BaseModel, field_validator


class CarbonIntensityRepo(Protocol):
def get_carbon_intensity(self) -> int:
async def get_carbon_intensity(self) -> int:
...


class InMemoryCarbonIntensityRepo(object):
def __init__(self, carbon_intensity: int) -> None:
self._carbon_intensity = carbon_intensity

def get_carbon_intensity(self) -> int:
async def get_carbon_intensity(self) -> int:
return self._carbon_intensity


class FromFileCarbonIntensityRepo(object):
def __init__(self, file_path: Path) -> None:
self._file_path = file_path

def get_carbon_intensity(self) -> int:
async def get_carbon_intensity(self) -> int:
return int(self._file_path.read_text(encoding="utf8"))


Expand All @@ -49,10 +49,10 @@ def ensure_data_is_not_empty(

class UkCarbonIntensityApiRepo:
def __init__(self, base_url: URL):
self._client = Client(base_url=base_url, http2=True)
self._client = AsyncClient(base_url=base_url, http2=True)

def get_carbon_intensity(self) -> int:
response = self._client.get("/intensity")
async def get_carbon_intensity(self) -> int:
response = await self._client.get("/intensity")
response.raise_for_status()

parsed_reponse = UkCarbonIntensityResponse.model_validate_json(response.content)
Expand Down
16 changes: 16 additions & 0 deletions tests/cli/test_async_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import ParamSpec, TypeVar

from src.cli.async_helper import async_to_sync

P = ParamSpec("P")
R = TypeVar("R")


def test_can_run_async_code_with_annotations():
@async_to_sync
async def async_func() -> str:
return "hello"

result = async_func()

assert result == "hello"
5 changes: 3 additions & 2 deletions tests/repos/test_carbon_intensity.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,11 @@ def carbon_intensity_repo(
def expected_carbon_intensity(self) -> int:
return 7

def test_gives_me_a_global_carbon_intensity_repo(
@pytest.mark.asyncio
async def test_gives_me_a_global_carbon_intensity_repo(
self, carbon_intensity_repo: CarbonIntensityRepo, expected_carbon_intensity: int
) -> None:
assert carbon_intensity_repo.get_carbon_intensity() == expected_carbon_intensity
assert await carbon_intensity_repo.get_carbon_intensity() == expected_carbon_intensity


def test_no_uk_carbon_intensity_data_raises() -> None:
Expand Down

0 comments on commit 675e723

Please sign in to comment.