diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a80c237..f94eeca 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.12.2" + ".": "1.13.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2072b87..6362bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 1.13.0 (2025-06-27) + +Full Changelog: [v1.12.2...v1.13.0](https://github.com/OneBusAway/python-sdk/compare/v1.12.2...v1.13.0) + +### Features + +* **client:** add support for aiohttp ([30eada9](https://github.com/OneBusAway/python-sdk/commit/30eada97ab7083d960307901bb55ef931ff6b202)) + + +### Bug Fixes + +* **ci:** release-doctor — report correct token name ([32bbbb3](https://github.com/OneBusAway/python-sdk/commit/32bbbb36efc1e3ebe55502aba1d140b0670e671c)) + + +### Chores + +* **tests:** skip some failing tests on the latest python versions ([6a88517](https://github.com/OneBusAway/python-sdk/commit/6a88517328513ff342df314e1d1da26795f3bb72)) + ## 1.12.2 (2025-06-19) Full Changelog: [v1.12.1...v1.12.2](https://github.com/OneBusAway/python-sdk/compare/v1.12.1...v1.12.2) diff --git a/README.md b/README.md index b30ea29..8502002 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,37 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. +### With aiohttp + +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. + +You can enable this by installing `aiohttp`: + +```sh +# install from PyPI +pip install onebusaway[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from onebusaway import DefaultAioHttpClient +from onebusaway import AsyncOnebusawaySDK + + +async def main() -> None: + async with AsyncOnebusawaySDK( + api_key=os.environ.get("ONEBUSAWAY_API_KEY"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + current_time = await client.current_time.retrieve() + + +asyncio.run(main()) +``` + ## Using types Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: diff --git a/bin/check-release-environment b/bin/check-release-environment index 7e1bf0e..b845b0f 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The ONEBUSAWAY_SDK_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi lenErrors=${#errors[@]} diff --git a/pyproject.toml b/pyproject.toml index fd7bf0a..e59c212 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "onebusaway" -version = "1.12.2" +version = "1.13.0" description = "The official Python library for the onebusaway-sdk API" dynamic = ["readme"] license = "Apache-2.0" @@ -37,6 +37,8 @@ classifiers = [ Homepage = "https://github.com/OneBusAway/python-sdk" Repository = "https://github.com/OneBusAway/python-sdk" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index e49ccf8..ced2807 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,6 +10,13 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via httpx-aiohttp + # via onebusaway +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 @@ -17,6 +24,10 @@ anyio==4.4.0 # via onebusaway argcomplete==3.1.2 # via nox +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -34,16 +45,23 @@ execnet==2.1.1 # via pytest-xdist filelock==3.12.4 # via virtualenv +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 + # via httpx-aiohttp # via onebusaway # via respx +httpx-aiohttp==0.1.6 + # via onebusaway idna==3.4 # via anyio # via httpx + # via yarl importlib-metadata==7.0.0 iniconfig==2.0.0 # via pytest @@ -51,6 +69,9 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py +multidict==6.4.4 + # via aiohttp + # via yarl mypy==1.14.1 mypy-extensions==1.0.0 # via mypy @@ -65,6 +86,9 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via onebusaway pydantic-core==2.27.1 @@ -97,6 +121,7 @@ tomli==2.0.2 # via pytest typing-extensions==4.12.2 # via anyio + # via multidict # via mypy # via onebusaway # via pydantic @@ -104,5 +129,7 @@ typing-extensions==4.12.2 # via pyright virtualenv==20.24.5 # via nox +yarl==1.20.0 + # via aiohttp zipp==3.17.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index cf5c8e0..f41f1d4 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,11 +10,22 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via httpx-aiohttp + # via onebusaway +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 # via httpx # via onebusaway +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -22,15 +33,28 @@ distro==1.8.0 # via onebusaway exceptiongroup==1.2.2 # via anyio +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 + # via httpx-aiohttp + # via onebusaway +httpx-aiohttp==0.1.6 # via onebusaway idna==3.4 # via anyio # via httpx + # via yarl +multidict==6.4.4 + # via aiohttp + # via yarl +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via onebusaway pydantic-core==2.27.1 @@ -40,6 +64,9 @@ sniffio==1.3.0 # via onebusaway typing-extensions==4.12.2 # via anyio + # via multidict # via onebusaway # via pydantic # via pydantic-core +yarl==1.20.0 + # via aiohttp diff --git a/src/onebusaway/__init__.py b/src/onebusaway/__init__.py index ea3444e..36af60f 100644 --- a/src/onebusaway/__init__.py +++ b/src/onebusaway/__init__.py @@ -36,7 +36,7 @@ UnprocessableEntityError, APIResponseValidationError, ) -from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging __all__ = [ @@ -78,6 +78,7 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] if not _t.TYPE_CHECKING: diff --git a/src/onebusaway/_base_client.py b/src/onebusaway/_base_client.py index 289eb37..0791689 100644 --- a/src/onebusaway/_base_client.py +++ b/src/onebusaway/_base_client.py @@ -1289,6 +1289,24 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + if TYPE_CHECKING: DefaultAsyncHttpxClient = httpx.AsyncClient """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK @@ -1297,8 +1315,12 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): diff --git a/src/onebusaway/_version.py b/src/onebusaway/_version.py index c1f99d9..6ef7cb3 100644 --- a/src/onebusaway/_version.py +++ b/src/onebusaway/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "onebusaway" -__version__ = "1.12.2" # x-release-please-version +__version__ = "1.13.0" # x-release-please-version diff --git a/tests/api_resources/test_agencies_with_coverage.py b/tests/api_resources/test_agencies_with_coverage.py index fad1087..b98f23a 100644 --- a/tests/api_resources/test_agencies_with_coverage.py +++ b/tests/api_resources/test_agencies_with_coverage.py @@ -44,7 +44,9 @@ def test_streaming_response_list(self, client: OnebusawaySDK) -> None: class TestAsyncAgenciesWithCoverage: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_agency.py b/tests/api_resources/test_agency.py index 05b83a8..d296efc 100644 --- a/tests/api_resources/test_agency.py +++ b/tests/api_resources/test_agency.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_arrival_and_departure.py b/tests/api_resources/test_arrival_and_departure.py index 0ba8109..88c379c 100644 --- a/tests/api_resources/test_arrival_and_departure.py +++ b/tests/api_resources/test_arrival_and_departure.py @@ -129,7 +129,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncArrivalAndDeparture: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_block.py b/tests/api_resources/test_block.py index a07a29b..86ba5c1 100644 --- a/tests/api_resources/test_block.py +++ b/tests/api_resources/test_block.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncBlock: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_config.py b/tests/api_resources/test_config.py index 62bdd91..9b19317 100644 --- a/tests/api_resources/test_config.py +++ b/tests/api_resources/test_config.py @@ -44,7 +44,9 @@ def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncConfig: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_current_time.py b/tests/api_resources/test_current_time.py index 1a05528..39a3b20 100644 --- a/tests/api_resources/test_current_time.py +++ b/tests/api_resources/test_current_time.py @@ -44,7 +44,9 @@ def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncCurrentTime: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_report_problem_with_stop.py b/tests/api_resources/test_report_problem_with_stop.py index 0b15883..ce177c0 100644 --- a/tests/api_resources/test_report_problem_with_stop.py +++ b/tests/api_resources/test_report_problem_with_stop.py @@ -69,7 +69,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncReportProblemWithStop: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_report_problem_with_trip.py b/tests/api_resources/test_report_problem_with_trip.py index 0bc9cf2..caa0730 100644 --- a/tests/api_resources/test_report_problem_with_trip.py +++ b/tests/api_resources/test_report_problem_with_trip.py @@ -74,7 +74,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncReportProblemWithTrip: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_route.py b/tests/api_resources/test_route.py index 8b773cf..8f52e08 100644 --- a/tests/api_resources/test_route.py +++ b/tests/api_resources/test_route.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncRoute: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_route_ids_for_agency.py b/tests/api_resources/test_route_ids_for_agency.py index 867c19a..df9b4b9 100644 --- a/tests/api_resources/test_route_ids_for_agency.py +++ b/tests/api_resources/test_route_ids_for_agency.py @@ -57,7 +57,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncRouteIDsForAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_routes_for_agency.py b/tests/api_resources/test_routes_for_agency.py index 1fe2d56..9b2182e 100644 --- a/tests/api_resources/test_routes_for_agency.py +++ b/tests/api_resources/test_routes_for_agency.py @@ -57,7 +57,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncRoutesForAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_routes_for_location.py b/tests/api_resources/test_routes_for_location.py index 55fbc31..fda2be8 100644 --- a/tests/api_resources/test_routes_for_location.py +++ b/tests/api_resources/test_routes_for_location.py @@ -65,7 +65,9 @@ def test_streaming_response_list(self, client: OnebusawaySDK) -> None: class TestAsyncRoutesForLocation: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_schedule_for_route.py b/tests/api_resources/test_schedule_for_route.py index 6973a7e..bd6f15f 100644 --- a/tests/api_resources/test_schedule_for_route.py +++ b/tests/api_resources/test_schedule_for_route.py @@ -66,7 +66,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncScheduleForRoute: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_schedule_for_stop.py b/tests/api_resources/test_schedule_for_stop.py index 0154e44..2035e05 100644 --- a/tests/api_resources/test_schedule_for_stop.py +++ b/tests/api_resources/test_schedule_for_stop.py @@ -66,7 +66,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncScheduleForStop: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_search_for_route.py b/tests/api_resources/test_search_for_route.py index 76114f6..af87522 100644 --- a/tests/api_resources/test_search_for_route.py +++ b/tests/api_resources/test_search_for_route.py @@ -58,7 +58,9 @@ def test_streaming_response_list(self, client: OnebusawaySDK) -> None: class TestAsyncSearchForRoute: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_search_for_stop.py b/tests/api_resources/test_search_for_stop.py index bed17ea..d6357a6 100644 --- a/tests/api_resources/test_search_for_stop.py +++ b/tests/api_resources/test_search_for_stop.py @@ -58,7 +58,9 @@ def test_streaming_response_list(self, client: OnebusawaySDK) -> None: class TestAsyncSearchForStop: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_shape.py b/tests/api_resources/test_shape.py index 369da7d..ec2c031 100644 --- a/tests/api_resources/test_shape.py +++ b/tests/api_resources/test_shape.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncShape: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_stop.py b/tests/api_resources/test_stop.py index 9286176..bb895b3 100644 --- a/tests/api_resources/test_stop.py +++ b/tests/api_resources/test_stop.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncStop: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_stop_ids_for_agency.py b/tests/api_resources/test_stop_ids_for_agency.py index 923a743..d7d48fe 100644 --- a/tests/api_resources/test_stop_ids_for_agency.py +++ b/tests/api_resources/test_stop_ids_for_agency.py @@ -57,7 +57,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncStopIDsForAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_stops_for_agency.py b/tests/api_resources/test_stops_for_agency.py index 7c6538e..c54df14 100644 --- a/tests/api_resources/test_stops_for_agency.py +++ b/tests/api_resources/test_stops_for_agency.py @@ -57,7 +57,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncStopsForAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_stops_for_location.py b/tests/api_resources/test_stops_for_location.py index fcb2f07..091e788 100644 --- a/tests/api_resources/test_stops_for_location.py +++ b/tests/api_resources/test_stops_for_location.py @@ -65,7 +65,9 @@ def test_streaming_response_list(self, client: OnebusawaySDK) -> None: class TestAsyncStopsForLocation: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_stops_for_route.py b/tests/api_resources/test_stops_for_route.py index e062f7d..da8c3dd 100644 --- a/tests/api_resources/test_stops_for_route.py +++ b/tests/api_resources/test_stops_for_route.py @@ -66,7 +66,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncStopsForRoute: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trip.py b/tests/api_resources/test_trip.py index ef2bd7f..3f2d180 100644 --- a/tests/api_resources/test_trip.py +++ b/tests/api_resources/test_trip.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncTrip: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trip_details.py b/tests/api_resources/test_trip_details.py index 82d00d9..6041ff6 100644 --- a/tests/api_resources/test_trip_details.py +++ b/tests/api_resources/test_trip_details.py @@ -69,7 +69,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncTripDetails: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trip_for_vehicle.py b/tests/api_resources/test_trip_for_vehicle.py index be96fa6..44981c2 100644 --- a/tests/api_resources/test_trip_for_vehicle.py +++ b/tests/api_resources/test_trip_for_vehicle.py @@ -68,7 +68,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncTripForVehicle: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trips_for_location.py b/tests/api_resources/test_trips_for_location.py index 050d552..236a09c 100644 --- a/tests/api_resources/test_trips_for_location.py +++ b/tests/api_resources/test_trips_for_location.py @@ -72,7 +72,9 @@ def test_streaming_response_list(self, client: OnebusawaySDK) -> None: class TestAsyncTripsForLocation: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trips_for_route.py b/tests/api_resources/test_trips_for_route.py index 0ef4092..c708aab 100644 --- a/tests/api_resources/test_trips_for_route.py +++ b/tests/api_resources/test_trips_for_route.py @@ -67,7 +67,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncTripsForRoute: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_vehicles_for_agency.py b/tests/api_resources/test_vehicles_for_agency.py index 5f201f2..88e709c 100644 --- a/tests/api_resources/test_vehicles_for_agency.py +++ b/tests/api_resources/test_vehicles_for_agency.py @@ -65,7 +65,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncVehiclesForAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index fad4436..dd1366e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,10 +6,12 @@ import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from onebusaway import OnebusawaySDK, AsyncOnebusawaySDK, DefaultAioHttpClient +from onebusaway._utils import is_dict if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] @@ -27,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -45,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[OnebusawaySDK]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncOnebusawaySDK]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") + + async with AsyncOnebusawaySDK( + base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client + ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 357cf79..ae9c4c2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -192,6 +192,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") @@ -995,6 +996,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo")