Skip to content

Commit 48051f7

Browse files
committed
Add functions to fetch forecast from OWM API
1 parent 6d074ce commit 48051f7

File tree

5 files changed

+136
-2
lines changed

5 files changed

+136
-2
lines changed

src/owm_api.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from os import getenv
2+
3+
from httpx import AsyncClient, ConnectTimeout
4+
from dotenv import load_dotenv
5+
6+
from constants.owm_api import DEFAULT_TIMEOUT, OPENWEATHERMAP_REQUEST_TEMPLATE
7+
from schemas import OWMWeather
8+
9+
load_dotenv()
10+
11+
12+
async def retrieve_weather_info() -> OWMWeather:
13+
raw_response = await fetch_weather()
14+
15+
return validate_response(raw_response)
16+
17+
18+
async def fetch_weather() -> dict:
19+
api_request = render_api_request()
20+
21+
async with AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
22+
try:
23+
response = await client.get(api_request)
24+
except ConnectTimeout:
25+
raise ConnectTimeout(
26+
message=f"Request to OpenWeatherMap API took more than {DEFAULT_TIMEOUT} seconds",
27+
)
28+
29+
response.raise_for_status()
30+
31+
return response.json()
32+
33+
34+
def render_api_request() -> str:
35+
if not (api_key := getenv("OPENWEATHERMAP_API_KEY")):
36+
raise ValueError("Env variable `OPENWEATHERMAP_API_KEY` is missing or empty")
37+
if not (latitude := getenv("LATITUDE")):
38+
raise ValueError("Env variable `LATITUDE` is missing or empty")
39+
if not (longitude := getenv("LONGITUDE")):
40+
raise ValueError("Env variable `LONGITUDE` is missing or empty")
41+
42+
return OPENWEATHERMAP_REQUEST_TEMPLATE.format(
43+
api_key=api_key, latitude=latitude, longitude=longitude
44+
)
45+
46+
47+
def validate_response(raw_response: dict) -> OWMWeather:
48+
return OWMWeather.from_dict(raw_response)

tests/conftest.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ class Meta:
2323

2424
message = factory.SubFactory(MessageFactory)
2525

26-
27-
@pytest.fixture(scope="module")
26+
@pytest.fixture
2827
def set_owm_api_env_variables():
28+
"""Do not add scope to this fixture, it will break unit tests at test_owm_api.py."""
2929
os.environ["OPENWEATHERMAP_API_KEY"] = "fake_app_id"
3030
os.environ["LATITUDE"] = "12.3456789"
3131
os.environ["LONGITUDE"] = "98.7654321"

tests/constants.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from typing import Final
22

3+
OPENWEATHERMAP_REQUEST: Final[str] = (
4+
"https://api.openweathermap.org/data/3.0/onecall?"
5+
"lat=12.3456789&lon=98.7654321&units=metric&exclude=minutely,hourly&appid=fake_app_id"
6+
)
7+
38

49
OWM_RESPONSE_FRAGMENT: Final[str] = """
510
{

tests/integration/test_owm_api.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pytest
2+
from httpx import ConnectTimeout, HTTPStatusError
3+
from pytest_httpx import HTTPXMock
4+
5+
from src.owm_api import fetch_weather, retrieve_weather_info
6+
from tests.constants import OPENWEATHERMAP_REQUEST, OWM_RESPONSE_FRAGMENT
7+
8+
9+
@pytest.mark.asyncio
10+
async def test_retrieve_weather_info(
11+
httpx_mock: HTTPXMock, fake_owmweather, set_owm_api_env_variables
12+
):
13+
httpx_mock.add_response(
14+
method="GET", url=OPENWEATHERMAP_REQUEST, text=OWM_RESPONSE_FRAGMENT
15+
)
16+
17+
owmweather_instance = await retrieve_weather_info()
18+
19+
assert owmweather_instance == fake_owmweather
20+
21+
22+
@pytest.mark.asyncio
23+
async def test_fetch_weather(httpx_mock: HTTPXMock, set_owm_api_env_variables):
24+
httpx_mock.add_response(method="GET", url=OPENWEATHERMAP_REQUEST, json={})
25+
26+
json_response = await fetch_weather()
27+
28+
assert json_response == {}
29+
30+
31+
@pytest.mark.asyncio
32+
async def test_fetch_weather__connection_timeout(
33+
httpx_mock: HTTPXMock, set_owm_api_env_variables
34+
):
35+
httpx_mock.add_exception(ConnectTimeout(""), url=OPENWEATHERMAP_REQUEST)
36+
37+
with pytest.raises(
38+
ConnectTimeout,
39+
match="Request to OpenWeatherMap API took more than 15.0 seconds",
40+
):
41+
await fetch_weather()
42+
43+
44+
@pytest.mark.asyncio
45+
async def test_fetch_weather__unauthorized(
46+
httpx_mock: HTTPXMock, set_owm_api_env_variables
47+
):
48+
httpx_mock.add_response(
49+
method="GET", url=OPENWEATHERMAP_REQUEST, json={}, status_code=401
50+
)
51+
52+
with pytest.raises(HTTPStatusError):
53+
await fetch_weather()

tests/unit/test_owm_api.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
from os import environ
3+
from constants.owm_api import OPENWEATHERMAP_REQUEST_TEMPLATE
4+
5+
from owm_api import render_api_request
6+
7+
8+
def test_render_api_request(set_owm_api_env_variables):
9+
expected_request = OPENWEATHERMAP_REQUEST_TEMPLATE.format(
10+
api_key="fake_app_id", latitude="12.3456789", longitude="98.7654321"
11+
)
12+
13+
assert render_api_request() == expected_request
14+
15+
16+
@pytest.mark.parametrize(
17+
argnames=("env_variable_name"),
18+
argvalues=("OPENWEATHERMAP_API_KEY", "LATITUDE", "LONGITUDE"),
19+
)
20+
def test_render_api_request_raises_value_error(
21+
env_variable_name, set_owm_api_env_variables
22+
):
23+
del environ[env_variable_name]
24+
25+
with pytest.raises(
26+
ValueError, match=f"Env variable `{env_variable_name}` is missing or empty"
27+
):
28+
render_api_request()

0 commit comments

Comments
 (0)