From 8fe87439ee87ca6055fdb203d79ab4adf876e3f7 Mon Sep 17 00:00:00 2001 From: 2tefan Date: Sun, 13 Sep 2026 20:34:15 +0200 Subject: [PATCH] Allow callers to provide an httpx client Allows Home Assistant and others to pass a shared client instead of creating a new client for every request. --- example.py | 25 +++++++++++++++++++++++++ luftdaten/__init__.py | 10 +++++++--- tests/test_data.py | 25 +++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/example.py b/example.py index 1eedccd..4884878 100644 --- a/example.py +++ b/example.py @@ -1,9 +1,12 @@ """Example for getting the data from a station.""" import asyncio +import httpx + from luftdaten import Luftdaten SENSOR_ID = 152 +SENSOR_IDS = (152, 153) async def main(): @@ -28,5 +31,27 @@ async def main(): ) +async def main_with_httpx_client(): + """Sample code using an existing HTTPX client for multiple sensors.""" + async with httpx.AsyncClient() as httpx_client: + # Reuse the same HTTPX session for all sensors. + for sensor_id in SENSOR_IDS: + data = Luftdaten(sensor_id, httpx_client=httpx_client) + await data.get_data() + + if not await data.validate_sensor(): + print("Station is not available:", data.sensor_id) + continue + + if data.values and data.meta: + print("Sensor values:", data.values) + print( + "Location:", + data.meta["latitude"], + data.meta["longitude"], + data.meta["altitude"], + ) + + if __name__ == "__main__": asyncio.run(main()) diff --git a/luftdaten/__init__.py b/luftdaten/__init__.py index 1c4a15f..ee050aa 100644 --- a/luftdaten/__init__.py +++ b/luftdaten/__init__.py @@ -12,21 +12,25 @@ class Luftdaten(object): """A class for handling the data retrieval.""" - def __init__(self, sensor_id): + def __init__(self, sensor_id, httpx_client=None): """Initialize the connection.""" self.sensor_id = sensor_id self.data = None self.values = {} self.meta = {} self.url = "{}/{}".format(_RESOURCE, "sensor") + self._httpx_client = httpx_client async def get_data(self): """Retrieve the data.""" url = "{}/{}/".format(self.url, self.sensor_id) try: - async with httpx.AsyncClient() as client: - response = await client.get(str(url)) + if self._httpx_client is not None: + response = await self._httpx_client.get(str(url)) + else: + async with httpx.AsyncClient() as client: + response = await client.get(str(url)) except httpx.ConnectError: raise exceptions.LuftdatenConnectionError(f"Connection to {url} failed") except httpx.ConnectTimeout: diff --git a/tests/test_data.py b/tests/test_data.py index 1187507..9058e61 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,4 +1,7 @@ """Test the interaction with the Luftdaten API.""" +from unittest.mock import AsyncMock, MagicMock + +import httpx import pytest from pytest_httpx import HTTPXMock @@ -67,6 +70,28 @@ async def test_sensor_values(httpx_mock: HTTPXMock): assert client.values == {"temperature": 10.5, "humidity": 79.3} +@pytest.mark.asyncio +async def test_injected_client(): + """Test that an injected HTTPX client is used and not closed.""" + httpx_client = AsyncMock(spec=httpx.AsyncClient) + response = MagicMock(spec=httpx.Response) + response.status_code = httpx.codes.OK + response.json.return_value = RESPONSE_VALID + httpx_client.get.return_value = response + + client = Luftdaten(SENSOR_ID, httpx_client=httpx_client) + await client.get_data() + + httpx_client.get.assert_awaited_once_with( + "https://data.sensor.community/airrohr/v1/sensor/1/" + ) + httpx_client.__aenter__.assert_not_awaited() + httpx_client.__aexit__.assert_not_awaited() + httpx_client.aclose.assert_not_awaited() + + assert client.values == {"temperature": 10.5, "humidity": 79.3} + + @pytest.mark.asyncio async def test_meta(httpx_mock: HTTPXMock): """Test the meta information."""