diff --git a/UnleashClient/_async_metrics.py b/UnleashClient/_async_metrics.py index 5e4d7eaf..6eb8cf20 100644 --- a/UnleashClient/_async_metrics.py +++ b/UnleashClient/_async_metrics.py @@ -5,7 +5,7 @@ from yggdrasil_engine.engine import UnleashEngine from UnleashClient._async_scheduler import _AsyncJob, _AsyncScheduler -from UnleashClient._async_transport import _AsyncTransport +from UnleashClient._async_unleash_api_client import _AsyncUnleashApiClient from UnleashClient._payloads import _build_metrics_payload from UnleashClient.config import UnleashConfig from UnleashClient.impact_metrics import ImpactMetrics @@ -23,7 +23,7 @@ class _AsyncMetricsReporter: reporter = _AsyncMetricsReporter( config=config, - transport=transport, + api_client=api_client, scheduler=scheduler, engine=engine, impact_metrics=impact_metrics, @@ -38,13 +38,13 @@ class _AsyncMetricsReporter: def __init__( self, config: UnleashConfig, - transport: _AsyncTransport, + api_client: _AsyncUnleashApiClient, scheduler: _AsyncScheduler, engine: UnleashEngine, impact_metrics: ImpactMetrics, ) -> None: self._config: UnleashConfig = config - self._transport: _AsyncTransport = transport + self._api: _AsyncUnleashApiClient = api_client self._scheduler: _AsyncScheduler = scheduler self._engine: UnleashEngine = engine self._impact_metrics: ImpactMetrics = impact_metrics @@ -75,7 +75,7 @@ async def flush(self) -> None: payload = _build_metrics_payload(self._config, bucket, impact_metrics) sent = False try: - sent = await self._transport.send_metrics(payload) + sent = await self._api.send_metrics(payload) finally: if not sent and impact_metrics: self._impact_metrics.restore(impact_metrics) diff --git a/UnleashClient/_async_transport.py b/UnleashClient/_async_unleash_api_client.py similarity index 94% rename from UnleashClient/_async_transport.py rename to UnleashClient/_async_unleash_api_client.py index 2ffec88d..c2885253 100644 --- a/UnleashClient/_async_transport.py +++ b/UnleashClient/_async_unleash_api_client.py @@ -1,4 +1,4 @@ -"""Asynchronous HTTP transport, for the async Unleash client.""" +"""Unleash API client over aiohttp, for the async Unleash client.""" import asyncio import json @@ -7,7 +7,7 @@ from UnleashClient._event_dispatcher import _EventDispatcher from UnleashClient._headers import _HeaderFactory -from UnleashClient._transport import ( +from UnleashClient._unleash_api_client import ( FetchResult, _emit_fetch_completed, _failure_status, @@ -42,8 +42,8 @@ async def _log_resp_info(resp: "aiohttp.ClientResponse") -> None: LOGGER.debug("HTTP content: %s", await resp.text()) -class _AsyncTransport: - """The asyncio twin of :class:`UnleashClient._transport._Transport`.""" +class _AsyncUnleashApiClient: + """The asyncio twin of :class:`UnleashClient._unleash_api_client._UnleashApiClient`.""" def __init__( self, @@ -82,10 +82,10 @@ def _timeout(self) -> "aiohttp.ClientTimeout": async def aclose(self) -> None: """ - Close the pooled session and put the transport out of service. + Close the pooled session and put the API client out of service. - Every later request raises :class:`AlreadyClosedError`. Closing a - transport that is already closed does nothing. + Every later request raises :class:`AlreadyClosedError`. Closing an already + closed API client does nothing. """ with self._close_lock: if self._is_closed: @@ -112,7 +112,7 @@ async def fetch_features( :param etag: the cached etag, or "" to fetch unconditionally. :param options: the polling settings - :raises AlreadyClosedError: if the transport has been closed. + :raises AlreadyClosedError: if the API client has been closed. """ self._raise_if_closed() @@ -202,7 +202,7 @@ async def register(self, payload: Dict[str, Any]) -> bool: :param payload: as built by :func:`UnleashClient._payloads._build_register_payload`. - :raises AlreadyClosedError: if the transport has been closed. + :raises AlreadyClosedError: if the API client has been closed. """ self._raise_if_closed() @@ -249,7 +249,7 @@ async def send_metrics(self, payload: Dict[str, Any]) -> bool: timeout all return False. :param payload: the metrics request body. - :raises AlreadyClosedError: if the transport has been closed. + :raises AlreadyClosedError: if the API client has been closed. :raises asyncio.CancelledError: if the send is cancelled. """ self._raise_if_closed() diff --git a/UnleashClient/_feature_store.py b/UnleashClient/_feature_store.py index efedb31e..2955efa7 100644 --- a/UnleashClient/_feature_store.py +++ b/UnleashClient/_feature_store.py @@ -31,7 +31,7 @@ class _FeatureStore: store = _FeatureStore(engine=UnleashEngine(), cache=cache, events=dispatcher) store.load_from_cache() - result = transport.fetch_features(etag=store.cached_etag, options=polling) + result = api_client.fetch_features(etag=store.cached_etag, options=polling) store.apply_fetched(raw_state=result.raw_state, etag=result.etag) """ diff --git a/UnleashClient/_metrics.py b/UnleashClient/_metrics.py index bf3c1694..aa5c5f12 100644 --- a/UnleashClient/_metrics.py +++ b/UnleashClient/_metrics.py @@ -4,7 +4,7 @@ from UnleashClient._payloads import _build_metrics_payload from UnleashClient._scheduler import _ScheduledJob, _Scheduler -from UnleashClient._transport import _Transport +from UnleashClient._unleash_api_client import _UnleashApiClient from UnleashClient.config import UnleashConfig from UnleashClient.impact_metrics import ImpactMetrics from UnleashClient.utils import LOGGER @@ -19,7 +19,7 @@ class _MetricsReporter: reporter = _MetricsReporter( config=config, - transport=transport, + api_client=api_client, scheduler=scheduler, engine=engine, impact_metrics=impact_metrics, @@ -34,13 +34,13 @@ class _MetricsReporter: def __init__( self, config: UnleashConfig, - transport: _Transport, + api_client: _UnleashApiClient, scheduler: _Scheduler, engine: UnleashEngine, impact_metrics: ImpactMetrics, ) -> None: self._config: UnleashConfig = config - self._transport: _Transport = transport + self._api: _UnleashApiClient = api_client self._scheduler: _Scheduler = scheduler self._engine: UnleashEngine = engine self._impact_metrics: ImpactMetrics = impact_metrics @@ -78,7 +78,7 @@ def flush(self) -> None: return payload = _build_metrics_payload(self._config, bucket, impact_metrics) - if not self._transport.send_metrics(payload) and impact_metrics: + if not self._api.send_metrics(payload) and impact_metrics: self._impact_metrics.restore(impact_metrics) def stop(self) -> None: diff --git a/UnleashClient/_transport.py b/UnleashClient/_unleash_api_client.py similarity index 99% rename from UnleashClient/_transport.py rename to UnleashClient/_unleash_api_client.py index 0bdda58b..d95f27c7 100644 --- a/UnleashClient/_transport.py +++ b/UnleashClient/_unleash_api_client.py @@ -84,7 +84,7 @@ def _log_resp_info(resp: Response) -> None: LOGGER.debug("HTTP content: %s", resp.text) -class _Transport: +class _UnleashApiClient: """ Sends the SDK's requests to the Unleash server: feature fetches, client registration and metrics submission. diff --git a/UnleashClient/clients/async_unleash_client.py b/UnleashClient/clients/async_unleash_client.py index 649d990e..fe7e1917 100644 --- a/UnleashClient/clients/async_unleash_client.py +++ b/UnleashClient/clients/async_unleash_client.py @@ -12,7 +12,7 @@ from UnleashClient._async_metrics import _AsyncMetricsReporter from UnleashClient._async_scheduler import _AsyncScheduler -from UnleashClient._async_transport import _AsyncTransport +from UnleashClient._async_unleash_api_client import _AsyncUnleashApiClient from UnleashClient._context import _ContextEnricher from UnleashClient._evaluator import _Evaluator from UnleashClient._event_dispatcher import _EventDispatcher @@ -142,13 +142,13 @@ def __init__( config=self._config, events=self._event_dispatcher, ) - self._transport: _AsyncTransport = _AsyncTransport( + self._api: _AsyncUnleashApiClient = _AsyncUnleashApiClient( self._config, self._headers, self._event_dispatcher ) self._metrics_scheduler: _AsyncScheduler = _AsyncScheduler() self._metrics: _AsyncMetricsReporter = _AsyncMetricsReporter( config=self._config, - transport=self._transport, + api_client=self._api, scheduler=self._metrics_scheduler, engine=self._engine, impact_metrics=self.impact_metrics, @@ -330,7 +330,7 @@ async def initialize_client(self, fetch_toggles: bool = True) -> None: self._engine.register_custom_strategies(self._custom_strategies) if not self._config.metrics.disable_registration: - await self._transport.register( + await self._api.register( _build_register_payload(self._config, self._custom_strategies) ) @@ -339,7 +339,7 @@ async def initialize_client(self, fetch_toggles: bool = True) -> None: self._connector = _AsyncPollingConnector( store=self._store, - transport=self._transport, + api_client=self._api, options=cast(PollingOptions, self._config.mode), ) await self._connector.start() @@ -384,7 +384,7 @@ async def destroy(self) -> None: except Exception as exc: LOGGER.warning("Exception during scheduler teardown: %s", exc) - await self._transport.aclose() + await self._api.aclose() if not isinstance(self._cache, FileCache): try: diff --git a/UnleashClient/clients/unleash_client.py b/UnleashClient/clients/unleash_client.py index 28bc0989..51aaeb62 100644 --- a/UnleashClient/clients/unleash_client.py +++ b/UnleashClient/clients/unleash_client.py @@ -21,7 +21,7 @@ from UnleashClient._metrics import _MetricsReporter from UnleashClient._payloads import _build_register_payload from UnleashClient._scheduler import _ScheduledJob, _Scheduler -from UnleashClient._transport import _Transport +from UnleashClient._unleash_api_client import _UnleashApiClient from UnleashClient.cache import BaseCache, FileCache from UnleashClient.config import ( HttpOptions, @@ -199,13 +199,13 @@ def __init__( events=self.__events, ) - self._transport = _Transport(self._config, self._headers, self.__events) + self._api = _UnleashApiClient(self._config, self._headers, self.__events) self._scheduler = _Scheduler(scheduler, scheduler_executor) self._metrics = _MetricsReporter( config=self._config, - transport=self._transport, + api_client=self._api, scheduler=self._scheduler, engine=self._engine, impact_metrics=self.impact_metrics, @@ -297,7 +297,7 @@ def initialize_client(self, fetch_toggles: bool = True) -> None: # Register app if not self._config.metrics.disable_registration: - self._transport.register( + self._api.register( _build_register_payload(self._config, self._custom_strategies) ) mode = self._config.mode @@ -315,7 +315,7 @@ def initialize_client(self, fetch_toggles: bool = True) -> None: self.connector = PollingConnector( store=self._store, scheduler=self._scheduler, - transport=self._transport, + api_client=self._api, options=mode, ) else: diff --git a/UnleashClient/connectors/_async_connector.py b/UnleashClient/connectors/_async_connector.py index a293de2c..592e9c59 100644 --- a/UnleashClient/connectors/_async_connector.py +++ b/UnleashClient/connectors/_async_connector.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from UnleashClient._async_scheduler import _AsyncScheduler -from UnleashClient._async_transport import _AsyncTransport +from UnleashClient._async_unleash_api_client import _AsyncUnleashApiClient from UnleashClient._feature_store import _FeatureStore from UnleashClient.config import PollingOptions @@ -32,7 +32,7 @@ class _AsyncPollingConnector(_AsyncBaseConnector): connector = _AsyncPollingConnector( store=store, - transport=transport, + api_client=api_client, options=PollingOptions(interval=15), ) await connector.start() @@ -43,21 +43,21 @@ class _AsyncPollingConnector(_AsyncBaseConnector): def __init__( self, store: _FeatureStore, - transport: _AsyncTransport, + api_client: _AsyncUnleashApiClient, options: PollingOptions, ) -> None: """ :param store: Applies feature state to the engine and the cache. - :param transport: Performs the fetch against the Unleash server. + :param api_client: Performs the fetch against the Unleash server. :param options: How often to fetch, and which project to fetch. """ super().__init__(store) - self._transport: _AsyncTransport = transport + self._api: _AsyncUnleashApiClient = api_client self._options: PollingOptions = options self._scheduler: _AsyncScheduler = _AsyncScheduler() async def _fetch_and_load(self) -> None: - result = await self._transport.fetch_features( + result = await self._api.fetch_features( etag=self._store.cached_etag, options=self._options ) diff --git a/UnleashClient/connectors/polling_connector.py b/UnleashClient/connectors/polling_connector.py index 1bffaa66..3e7372de 100644 --- a/UnleashClient/connectors/polling_connector.py +++ b/UnleashClient/connectors/polling_connector.py @@ -1,6 +1,6 @@ from UnleashClient._feature_store import _FeatureStore from UnleashClient._scheduler import _ScheduledJob, _Scheduler -from UnleashClient._transport import _Transport +from UnleashClient._unleash_api_client import _UnleashApiClient from UnleashClient.config import PollingOptions from .base_connector import BaseConnector @@ -13,21 +13,21 @@ def __init__( self, store: _FeatureStore, scheduler: _Scheduler, - transport: _Transport, + api_client: _UnleashApiClient, options: PollingOptions, ): """ - :param transport: Performs the fetch against the Unleash server. + :param api_client: Performs the fetch against the Unleash server. :param options: How often to fetch, and which project to fetch. """ super().__init__(store) self.scheduler: _Scheduler = scheduler - self.transport: _Transport = transport + self.api_client: _UnleashApiClient = api_client self.options: PollingOptions = options self.job: _ScheduledJob = None def _fetch_and_load(self) -> None: - result = self.transport.fetch_features( + result = self.api_client.fetch_features( self._store.cached_etag, options=self.options ) diff --git a/UnleashClient/errors.py b/UnleashClient/errors.py index ad23ce67..9e3b957b 100644 --- a/UnleashClient/errors.py +++ b/UnleashClient/errors.py @@ -94,7 +94,7 @@ class TransportError(UnleashClientError): Example:: try: - result = await transport.fetch_features(options=polling) + result = await api_client.fetch_features(options=polling) except TransportError as error: LOGGER.error("Could not reach the Unleash server: %s", error) """ @@ -108,10 +108,10 @@ class AlreadyClosedError(TransportError): Example:: - await transport.aclose() + await api_client.aclose() try: - await transport.fetch_features(options=polling) + await api_client.fetch_features(options=polling) except AlreadyClosedError: ... # build a new client instead of reusing the closed one """ diff --git a/tests/unit_tests/clients/test_async_unleash_client.py b/tests/unit_tests/clients/test_async_unleash_client.py index cb54cfb6..22f59a1e 100644 --- a/tests/unit_tests/clients/test_async_unleash_client.py +++ b/tests/unit_tests/clients/test_async_unleash_client.py @@ -376,7 +376,7 @@ def test_async_client_builds_a_metrics_reporter_over_its_own_collaborators(tmpdi client = build_async_client(tmpdir, url=URL, app_name=APP_NAME) assert client._metrics._config is client._config - assert client._metrics._transport is client._transport + assert client._metrics._api is client._api assert client._metrics._engine is client._engine assert client._metrics._impact_metrics is client.impact_metrics @@ -539,14 +539,14 @@ async def test_initializing_awaits_the_first_fetch_while_yielding_to_the_event_l client = build_running_client(mode=PollingOptions(interval=3600)) started = asyncio.Event() release = asyncio.Event() - fetch_features = client._transport.fetch_features + fetch_features = client._api.fetch_features async def delayed_fetch(**kwargs): started.set() await release.wait() return await fetch_features(**kwargs) - monkeypatch.setattr(client._transport, "fetch_features", delayed_fetch) + monkeypatch.setattr(client._api, "fetch_features", delayed_fetch) initializing = asyncio.create_task(client.initialize_client()) try: await asyncio.wait_for(started.wait(), WAIT_TIMEOUT) @@ -706,7 +706,7 @@ async def test_destroy_during_registration_leaves_nothing_polling( ): server.on("GET", FEATURES_PATH, payload=MOCK_FEATURE_RESPONSE, repeat=True) client = build_running_client(mode=PollingOptions(interval=0.01)) - client._transport.register = lambda payload: asyncio.sleep(0.05) + client._api.register = lambda payload: asyncio.sleep(0.05) initializing = asyncio.ensure_future(client.initialize_client()) await asyncio.sleep(0) diff --git a/tests/unit_tests/connectors/test_async_connector.py b/tests/unit_tests/connectors/test_async_connector.py index b97b2723..1a94edf5 100644 --- a/tests/unit_tests/connectors/test_async_connector.py +++ b/tests/unit_tests/connectors/test_async_connector.py @@ -10,7 +10,7 @@ from tests.utilities.fake_unleash_server import FakeUnleash from tests.utilities.mocks.mock_features import MOCK_FEATURE_RESPONSE from tests.utilities.testing_constants import APP_NAME, ETAG_VALUE -from UnleashClient._async_transport import _AsyncTransport +from UnleashClient._async_unleash_api_client import _AsyncUnleashApiClient from UnleashClient._event_dispatcher import _EventDispatcher from UnleashClient._feature_store import _FeatureStore from UnleashClient._headers import _HeaderFactory @@ -46,21 +46,21 @@ def _build_connector( refresh_jitter: float = None, ) -> _AsyncPollingConnector: config = UnleashConfig(server.base_url, APP_NAME, http=HttpOptions(retries=0)) - transport = _AsyncTransport(config, _HeaderFactory(config)) + api_client = _AsyncUnleashApiClient(config, _HeaderFactory(config)) connector = _AsyncPollingConnector( store=store, - transport=transport, + api_client=api_client, options=PollingOptions(interval=refresh_interval, jitter=refresh_jitter), ) - built.append((connector, transport)) + built.append((connector, api_client)) return connector try: yield _build_connector finally: - for connector, transport in built: + for connector, api_client in built: await connector.stop() - await transport.aclose() + await api_client.aclose() async def until(predicate: Callable[[], bool]) -> None: diff --git a/tests/unit_tests/connectors/test_polling_connector.py b/tests/unit_tests/connectors/test_polling_connector.py index 87eac380..6ecc3bf5 100644 --- a/tests/unit_tests/connectors/test_polling_connector.py +++ b/tests/unit_tests/connectors/test_polling_connector.py @@ -22,7 +22,7 @@ from UnleashClient._feature_store import _FeatureStore from UnleashClient._headers import _HeaderFactory from UnleashClient._scheduler import _Scheduler -from UnleashClient._transport import _Transport +from UnleashClient._unleash_api_client import _UnleashApiClient from UnleashClient.config import ( HttpOptions, IdentityOptions, @@ -36,7 +36,7 @@ FULL_FEATURE_URL = URL + FEATURES_URL -def build_transport(**kwargs) -> _Transport: +def build_api_client(**kwargs) -> _UnleashApiClient: defaults = { "identity": IdentityOptions(instance_id=INSTANCE_ID), "http": HttpOptions( @@ -48,7 +48,7 @@ def build_transport(**kwargs) -> _Transport: } defaults.update(kwargs) config = UnleashConfig(URL, APP_NAME, **defaults) - return _Transport(config, _HeaderFactory(config)) + return _UnleashApiClient(config, _HeaderFactory(config)) @responses.activate @@ -67,7 +67,7 @@ def test_polling_connector_fetch_and_load(cache_empty): connector = PollingConnector( store=_FeatureStore(engine=engine, cache=temp_cache), scheduler=scheduler, - transport=build_transport(), + api_client=build_api_client(), options=PollingOptions(), ) @@ -89,7 +89,7 @@ def test_polling_connector_fetch_and_load_project(cache_empty): connector = PollingConnector( store=_FeatureStore(engine=engine, cache=temp_cache), scheduler=scheduler, - transport=build_transport(), + api_client=build_api_client(), options=PollingOptions(project_name=PROJECT_NAME), ) @@ -110,7 +110,7 @@ def test_polling_connector_fetch_and_load_failure(cache_empty): connector = PollingConnector( store=_FeatureStore(engine=engine, cache=temp_cache), scheduler=scheduler, - transport=build_transport(), + api_client=build_api_client(), options=PollingOptions(), ) @@ -141,7 +141,7 @@ def test_polling_connector_emits_fetched_and_ready( connector = PollingConnector( store=_FeatureStore(engine=engine, cache=cache_empty, events=dispatcher), scheduler=scheduler, - transport=build_transport(), + api_client=build_api_client(), # Huge refresh interval to avoid any polling during the test. That # way we avoid having to call _fetch_and_load() directly and can test the start/stop behavior. options=PollingOptions(interval=10), @@ -168,7 +168,7 @@ def test_polling_connector_emits_ready_once_across_polls( connector = PollingConnector( store=_FeatureStore(engine=engine, cache=cache_empty, events=dispatcher), scheduler=scheduler, - transport=build_transport(), + api_client=build_api_client(), options=PollingOptions(), ) @@ -201,7 +201,7 @@ def test_polling_connector_start_stop(cache_empty): connector = PollingConnector( store=_FeatureStore(engine=engine, cache=temp_cache), scheduler=scheduler, - transport=build_transport(), + api_client=build_api_client(), options=PollingOptions(interval=1), ) diff --git a/tests/unit_tests/test_async_metrics.py b/tests/unit_tests/test_async_metrics.py index 8b40b5f1..9b05823f 100644 --- a/tests/unit_tests/test_async_metrics.py +++ b/tests/unit_tests/test_async_metrics.py @@ -10,7 +10,7 @@ from tests.utilities.fake_unleash_server import FakeUnleash from UnleashClient._async_metrics import _AsyncMetricsReporter from UnleashClient._async_scheduler import _AsyncJob, _AsyncJobFn, _AsyncScheduler -from UnleashClient._async_transport import _AsyncTransport +from UnleashClient._async_unleash_api_client import _AsyncUnleashApiClient from UnleashClient._headers import _HeaderFactory from UnleashClient.config import IdentityOptions, MetricsOptions, UnleashConfig from UnleashClient.constants import CLIENT_SPEC_VERSION, METRICS_URL @@ -18,7 +18,7 @@ APP_NAME = "pytest" -# The fake server mounts Unleash under /api, so the async transport builds the URL +# The fake server mounts Unleash under /api, so the async API client builds the URL # shape it builds against a deployment. API_PREFIX = "/api" METRICS_PATH = API_PREFIX + METRICS_URL @@ -121,7 +121,7 @@ def _build_reporter(impact_metrics=None, **kwargs) -> _AsyncMetricsReporter: engine = UnleashEngine() reporter = _AsyncMetricsReporter( config=config, - transport=_AsyncTransport(config, _HeaderFactory(config)), + api_client=_AsyncUnleashApiClient(config, _HeaderFactory(config)), scheduler=RecordingAsyncScheduler(), engine=engine, impact_metrics=( @@ -138,7 +138,7 @@ def _build_reporter(impact_metrics=None, **kwargs) -> _AsyncMetricsReporter: finally: for reporter in built: await reporter._scheduler.shutdown() - await reporter._transport.aclose() + await reporter._api.aclose() @pytest_asyncio.fixture async def reporter( @@ -225,11 +225,11 @@ async def test_flush_omits_sdk_flavor_when_unset(self, server, reporter): assert "sdkFlavorVersion" not in body @mark.asyncio - async def test_the_flush_goes_through_the_async_transport(self, reporter): - # The flush runs on the client's loop, so a blocking transport would hold it up for + async def test_the_flush_goes_through_the_async_api_client(self, reporter): + # The flush runs on the client's loop, so a blocking API client would hold it up for # the length of every POST. - assert isinstance(reporter._transport, _AsyncTransport) - assert asyncio.iscoroutinefunction(reporter._transport.send_metrics) + assert isinstance(reporter._api, _AsyncUnleashApiClient) + assert asyncio.iscoroutinefunction(reporter._api.send_metrics) assert asyncio.iscoroutinefunction(reporter.flush) # start and stop @@ -309,7 +309,7 @@ async def test_constructing_the_reporter_registers_no_job_and_opens_no_session( reporter = build_reporter() assert reporter._scheduler.registered == [] - assert reporter._transport._session is None + assert reporter._api._session is None # impact metrics diff --git a/tests/unit_tests/test_async_transport.py b/tests/unit_tests/test_async_unleash_api_client.py similarity index 74% rename from tests/unit_tests/test_async_transport.py rename to tests/unit_tests/test_async_unleash_api_client.py index 692d1f85..6c9a873a 100644 --- a/tests/unit_tests/test_async_transport.py +++ b/tests/unit_tests/test_async_unleash_api_client.py @@ -22,7 +22,7 @@ REQUEST_RETRIES, REQUEST_TIMEOUT, ) -from UnleashClient._async_transport import _AsyncTransport +from UnleashClient._async_unleash_api_client import _AsyncUnleashApiClient from UnleashClient._headers import _HeaderFactory from UnleashClient.config import ( HttpOptions, @@ -58,14 +58,14 @@ async def server(): @pytest_asyncio.fixture -async def build_transport(server: FakeUnleash): +async def build_api_client(server: FakeUnleash): """ Factory pointed at the fake server. Keyword arguments override the defaults - on the config. Every transport it builds is closed on teardown. + on the config. Every API client it builds is closed on teardown. """ built = [] - def _build_transport(events=None, **kwargs) -> _AsyncTransport: + def _build_api_client(events=None, **kwargs) -> _AsyncUnleashApiClient: defaults = { "url": server.base_url, "app_name": APP_NAME, @@ -78,21 +78,23 @@ def _build_transport(events=None, **kwargs) -> _AsyncTransport: } defaults.update(kwargs) config = UnleashConfig(**defaults) - transport = _AsyncTransport(config, _HeaderFactory(config), events) - built.append(transport) - return transport + api_client = _AsyncUnleashApiClient(config, _HeaderFactory(config), events) + built.append(api_client) + return api_client try: - yield _build_transport + yield _build_api_client finally: - for transport in built: - await transport.aclose() + for api_client in built: + await api_client.aclose() @pytest_asyncio.fixture -async def transport(build_transport: Callable[..., _AsyncTransport]) -> _AsyncTransport: - """The transport the tests that need no config override share.""" - return build_transport() +async def api_client( + build_api_client: Callable[..., _AsyncUnleashApiClient], +) -> _AsyncUnleashApiClient: + """The API client the tests that need no config override share.""" + return build_api_client() @mark.asyncio @@ -111,7 +113,7 @@ async def transport(build_transport: Callable[..., _AsyncTransport]) -> _AsyncTr ), ) async def test_fetch_features( - server, transport, response, status, call_count, expected + server, api_client, response, status, call_count, expected ): server.on( "GET", @@ -122,7 +124,7 @@ async def test_fetch_features( repeat=True, ) - result = await transport.fetch_features(options=PollingOptions()) + result = await api_client.fetch_features(options=PollingOptions()) assert len(server.calls("GET", FEATURES_PATH)) == call_count assert expected(result.raw_state) @@ -130,9 +132,9 @@ async def test_fetch_features( @mark.asyncio async def test_fetch_features_sends_the_project_as_a_query_parameter( - server, build_transport + server, build_api_client ): - transport = build_transport() + api_client = build_api_client() server.on( "GET", FEATURES_PATH, @@ -141,7 +143,7 @@ async def test_fetch_features_sends_the_project_as_a_query_parameter( headers={"etag": ETAG_VALUE}, ) - result = await transport.fetch_features( + result = await api_client.fetch_features( options=PollingOptions(project_name=PROJECT_NAME) ) @@ -153,8 +155,8 @@ async def test_fetch_features_sends_the_project_as_a_query_parameter( @mark.asyncio -async def test_fetch_features_drops_the_etag_on_failure(server, build_transport): - transport = build_transport() +async def test_fetch_features_drops_the_etag_on_failure(server, build_api_client): + api_client = build_api_client() server.on( "GET", FEATURES_PATH, @@ -164,7 +166,7 @@ async def test_fetch_features_drops_the_etag_on_failure(server, build_transport) repeat=True, ) - result = await transport.fetch_features( + result = await api_client.fetch_features( options=PollingOptions(project_name=PROJECT_NAME) ) @@ -175,12 +177,12 @@ async def test_fetch_features_drops_the_etag_on_failure(server, build_transport) @mark.asyncio async def test_fetch_features_sends_if_none_match_when_an_etag_is_known( - server, build_transport + server, build_api_client ): - transport = build_transport() + api_client = build_api_client() server.on("GET", FEATURES_PATH, status=304, headers={"etag": ETAG_VALUE}) - result = await transport.fetch_features( + result = await api_client.fetch_features( ETAG_VALUE, options=PollingOptions(project_name=PROJECT_NAME) ) @@ -193,8 +195,8 @@ async def test_fetch_features_sends_if_none_match_when_an_etag_is_known( @mark.asyncio -async def test_fetch_features_retries_and_recovers(server, build_transport): - transport = build_transport() +async def test_fetch_features_retries_and_recovers(server, build_api_client): + api_client = build_api_client() server.on("GET", FEATURES_PATH, status=500, payload={}) server.on( "GET", @@ -204,7 +206,7 @@ async def test_fetch_features_retries_and_recovers(server, build_transport): headers={"etag": ETAG_VALUE}, ) - result = await transport.fetch_features( + result = await api_client.fetch_features( ETAG_VALUE, options=PollingOptions(project_name=PROJECT_NAME) ) @@ -215,9 +217,9 @@ async def test_fetch_features_retries_and_recovers(server, build_transport): @mark.asyncio async def test_fetch_features_retries_and_recovers_from_a_connection_error( - server, build_transport + server, build_api_client ): - transport = build_transport() + api_client = build_api_client() server.on("GET", FEATURES_PATH, disconnect=True) server.on( "GET", @@ -227,7 +229,7 @@ async def test_fetch_features_retries_and_recovers_from_a_connection_error( headers={"etag": ETAG_VALUE}, ) - result = await transport.fetch_features( + result = await api_client.fetch_features( ETAG_VALUE, options=PollingOptions(project_name=PROJECT_NAME) ) @@ -245,20 +247,20 @@ async def test_fetch_features_retries_and_recovers_from_a_connection_error( param({"disconnect": True}, False, id="connection-error"), ), ) -async def test_register(server, transport, reply, expected): +async def test_register(server, api_client, reply, expected): server.on("POST", REGISTER_PATH, **reply) - result = await transport.register({"appName": APP_NAME}) + result = await api_client.register({"appName": APP_NAME}) assert len(server.calls("POST", REGISTER_PATH)) == 1 assert result is expected @mark.asyncio -async def test_register_sends_the_payload_it_is_given(server, transport): +async def test_register_sends_the_payload_it_is_given(server, api_client): server.on("POST", REGISTER_PATH, status=202, payload={}) - await transport.register({"appName": APP_NAME, "strategies": []}) + await api_client.register({"appName": APP_NAME, "strategies": []}) body = server.calls("POST", REGISTER_PATH)[0].body assert json.loads(body) == {"appName": APP_NAME, "strategies": []} @@ -278,22 +280,22 @@ def skip_url_validation(monkeypatch): @mark.parametrize( "url", ("thisisnotavalidurl", "localhost:4242/api"), ids=("no-scheme", "bad-scheme") ) -async def test_register_reraises_an_unusable_url(server, build_transport, url): - transport = build_transport(url=url) +async def test_register_reraises_an_unusable_url(server, build_api_client, url): + api_client = build_api_client(url=url) with pytest.raises(aiohttp.ClientError): - await transport.register({}) + await api_client.register({}) assert server.requests == [] @mark.asyncio @mark.usefixtures("skip_url_validation") -async def test_register_reraises_a_missing_scheme_as_a_value_error(build_transport): - transport = build_transport(url="thisisnotavalidurl") +async def test_register_reraises_a_missing_scheme_as_a_value_error(build_api_client): + api_client = build_api_client(url="thisisnotavalidurl") with pytest.raises(ValueError): - await transport.register({}) + await api_client.register({}) @mark.asyncio @@ -306,10 +308,10 @@ async def test_register_reraises_a_missing_scheme_as_a_value_error(build_transpo param({"disconnect": True}, False, id="connection-error"), ), ) -async def test_send_metrics(server, transport, reply, expected): +async def test_send_metrics(server, api_client, reply, expected): server.on("POST", METRICS_PATH, **reply) - result = await transport.send_metrics(MOCK_METRICS_REQUEST) + result = await api_client.send_metrics(MOCK_METRICS_REQUEST) calls = server.calls("POST", METRICS_PATH) assert len(calls) == 1 @@ -321,21 +323,21 @@ async def test_send_metrics(server, transport, reply, expected): @mark.asyncio async def test_send_metrics_returns_false_when_the_request_times_out( - server, build_transport + server, build_api_client ): - transport = build_transport(http=HttpOptions(timeout=0.1)) + api_client = build_api_client(http=HttpOptions(timeout=0.1)) server.on("POST", METRICS_PATH, hang=True) - result = await transport.send_metrics(MOCK_METRICS_REQUEST) + result = await api_client.send_metrics(MOCK_METRICS_REQUEST) assert len(server.calls("POST", METRICS_PATH)) == 1 assert result is False @mark.asyncio -async def test_send_metrics_raises_when_cancelled(server, transport): +async def test_send_metrics_raises_when_cancelled(server, api_client): server.on("POST", METRICS_PATH, hang=True) - send = asyncio.create_task(transport.send_metrics(MOCK_METRICS_REQUEST)) + send = asyncio.create_task(api_client.send_metrics(MOCK_METRICS_REQUEST)) while not server.calls("POST", METRICS_PATH): await asyncio.sleep(0.01) @@ -347,16 +349,16 @@ async def test_send_metrics_raises_when_cancelled(server, transport): @mark.asyncio async def test_every_endpoint_gets_its_headers_from_the_factory( - server, build_transport + server, build_api_client ): - transport = build_transport(metrics=MetricsOptions(interval=2)) + api_client = build_api_client(metrics=MetricsOptions(interval=2)) server.on("GET", FEATURES_PATH, status=200, payload=MOCK_FEATURE_RESPONSE) server.on("POST", REGISTER_PATH, status=202, payload={}) server.on("POST", METRICS_PATH, status=202, payload={}) - await transport.fetch_features(options=PollingOptions(interval=1)) - await transport.register({}) - await transport.send_metrics(MOCK_METRICS_REQUEST) + await api_client.fetch_features(options=PollingOptions(interval=1)) + await api_client.register({}) + await api_client.send_metrics(MOCK_METRICS_REQUEST) fetch = server.calls("GET", FEATURES_PATH)[0].headers register = server.calls("POST", REGISTER_PATH)[0].headers @@ -371,15 +373,15 @@ async def test_every_endpoint_gets_its_headers_from_the_factory( @mark.asyncio -async def test_the_session_is_reused_across_requests(server, transport): +async def test_the_session_is_reused_across_requests(server, api_client): server.on("POST", METRICS_PATH, status=202, payload={}, repeat=True) - await transport.send_metrics(MOCK_METRICS_REQUEST) - first = transport._session - await transport.send_metrics(MOCK_METRICS_REQUEST) + await api_client.send_metrics(MOCK_METRICS_REQUEST) + first = api_client._session + await api_client.send_metrics(MOCK_METRICS_REQUEST) assert first is not None - assert transport._session is first + assert api_client._session is first @mark.asyncio @@ -387,29 +389,29 @@ async def test_the_session_is_reused_across_requests(server, transport): "call", ( param( - lambda transport: transport.fetch_features(options=PollingOptions()), + lambda api_client: api_client.fetch_features(options=PollingOptions()), id="fetch_features", ), - param(lambda transport: transport.register({}), id="register"), + param(lambda api_client: api_client.register({}), id="register"), param( - lambda transport: transport.send_metrics(MOCK_METRICS_REQUEST), + lambda api_client: api_client.send_metrics(MOCK_METRICS_REQUEST), id="send_metrics", ), ), ) -async def test_a_closed_transport_refuses_every_request(server, transport, call): - await transport.aclose() +async def test_a_closed_api_client_refuses_every_request(server, api_client, call): + await api_client.aclose() with pytest.raises(AlreadyClosedError): - await call(transport) + await call(api_client) assert server.requests == [] @mark.asyncio -async def test_closing_a_closed_transport_does_nothing(transport): - await transport.aclose() - await transport.aclose() +async def test_closing_a_closed_api_client_does_nothing(api_client): + await api_client.aclose() + await api_client.aclose() class RecordingEvents: @@ -448,31 +450,31 @@ def fetch_outcomes(events: RecordingEvents): ), ) async def test_fetch_features_reports_how_the_fetch_ended( - server, build_transport, status, payload, expected + server, build_api_client, status, payload, expected ): server.on("GET", FEATURES_PATH, status=status, payload=payload, repeat=True) events = RecordingEvents() - await build_transport(events=events).fetch_features(options=PollingOptions()) + await build_api_client(events=events).fetch_features(options=PollingOptions()) assert fetch_outcomes(events) == [expected] @mark.asyncio async def test_fetch_features_reports_unavailable_when_no_response_arrives( - server, build_transport + server, build_api_client ): server.on("GET", FEATURES_PATH, disconnect=True, repeat=True) events = RecordingEvents() - await build_transport(events=events).fetch_features(options=PollingOptions()) + await build_api_client(events=events).fetch_features(options=PollingOptions()) assert fetch_outcomes(events) == [FetchStatus.UNAVAILABLE] @mark.asyncio async def test_fetch_features_reports_unavailable_when_the_body_times_out( - server, build_transport, monkeypatch + server, build_api_client, monkeypatch ): # A 200 status line arrives but its body never does. That is the server # being unreachable, not a bad response. @@ -484,6 +486,6 @@ async def body_times_out(_response): monkeypatch.setattr(aiohttp.ClientResponse, "text", body_times_out) events = RecordingEvents() - await build_transport(events=events).fetch_features(options=PollingOptions()) + await build_api_client(events=events).fetch_features(options=PollingOptions()) assert fetch_outcomes(events) == [FetchStatus.UNAVAILABLE] diff --git a/tests/unit_tests/test_metrics.py b/tests/unit_tests/test_metrics.py index c6d710ac..5dcc800e 100644 --- a/tests/unit_tests/test_metrics.py +++ b/tests/unit_tests/test_metrics.py @@ -7,7 +7,7 @@ from UnleashClient._headers import _HeaderFactory from UnleashClient._metrics import _MetricsReporter from UnleashClient._scheduler import _Scheduler -from UnleashClient._transport import _Transport +from UnleashClient._unleash_api_client import _UnleashApiClient from UnleashClient.config import IdentityOptions, MetricsOptions, UnleashConfig from UnleashClient.constants import CLIENT_SPEC_VERSION, METRICS_URL from UnleashClient.impact_metrics import ImpactMetrics @@ -72,7 +72,7 @@ def build_sync_reporter( engine = UnleashEngine() return _MetricsReporter( config=config, - transport=_Transport(config, _HeaderFactory(config)), + api_client=_UnleashApiClient(config, _HeaderFactory(config)), scheduler=scheduler if scheduler is not None else _Scheduler(), engine=engine, impact_metrics=( diff --git a/tests/unit_tests/test_transport.py b/tests/unit_tests/test_unleash_api_client.py similarity index 78% rename from tests/unit_tests/test_transport.py rename to tests/unit_tests/test_unleash_api_client.py index df636aa3..c556b005 100644 --- a/tests/unit_tests/test_transport.py +++ b/tests/unit_tests/test_unleash_api_client.py @@ -24,7 +24,7 @@ URL, ) from UnleashClient._headers import _HeaderFactory -from UnleashClient._transport import _Transport +from UnleashClient._unleash_api_client import _UnleashApiClient from UnleashClient.config import ( HttpOptions, IdentityOptions, @@ -46,10 +46,10 @@ @pytest.fixture -def build_transport(): +def build_api_client(): """Factory. Keyword arguments override the defaults on the config.""" - def _build_transport(events=None, **kwargs) -> _Transport: + def _build_api_client(events=None, **kwargs) -> _UnleashApiClient: defaults = { "url": URL, "app_name": APP_NAME, @@ -63,14 +63,14 @@ def _build_transport(events=None, **kwargs) -> _Transport: } defaults.update(kwargs) config = UnleashConfig(**defaults) - return _Transport(config, _HeaderFactory(config), events) + return _UnleashApiClient(config, _HeaderFactory(config), events) - return _build_transport + return _build_api_client @pytest.fixture -def transport(build_transport) -> _Transport: - return build_transport() +def api_client(build_api_client) -> _UnleashApiClient: + return build_api_client() # fetch_features @@ -91,7 +91,7 @@ def transport(build_transport) -> _Transport: param({}, 500, 4, lambda result: not result, id="failure"), ), ) -def test_fetch_features(transport, response, status, calls, expected): +def test_fetch_features(api_client, response, status, calls, expected): responses.add( responses.GET, FULL_FEATURE_URL, @@ -100,7 +100,7 @@ def test_fetch_features(transport, response, status, calls, expected): headers={"etag": ETAG_VALUE}, ) - result = transport.fetch_features(options=PollingOptions()) + result = api_client.fetch_features(options=PollingOptions()) # 4 calls on a 500 is 1 + REQUEST_RETRIES: the mounted HTTPAdapter/Retry # has to survive every move of this code. @@ -109,7 +109,7 @@ def test_fetch_features(transport, response, status, calls, expected): @responses.activate -def test_fetch_features_sends_the_project_as_a_query_parameter(build_transport): +def test_fetch_features_sends_the_project_as_a_query_parameter(build_api_client): responses.add( responses.GET, PROJECT_URL, @@ -118,7 +118,7 @@ def test_fetch_features_sends_the_project_as_a_query_parameter(build_transport): headers={"etag": ETAG_VALUE}, ) - result = build_transport().fetch_features( + result = build_api_client().fetch_features( options=PollingOptions(project_name=PROJECT_NAME) ) @@ -128,12 +128,12 @@ def test_fetch_features_sends_the_project_as_a_query_parameter(build_transport): @responses.activate -def test_fetch_features_drops_the_etag_on_failure(build_transport): +def test_fetch_features_drops_the_etag_on_failure(build_api_client): responses.add( responses.GET, PROJECT_URL, json={}, status=500, headers={"etag": ETAG_VALUE} ) - result = build_transport().fetch_features( + result = build_api_client().fetch_features( options=PollingOptions(project_name=PROJECT_NAME) ) @@ -143,10 +143,10 @@ def test_fetch_features_drops_the_etag_on_failure(build_transport): @responses.activate -def test_fetch_features_sends_if_none_match_when_an_etag_is_known(build_transport): +def test_fetch_features_sends_if_none_match_when_an_etag_is_known(build_api_client): responses.add(responses.GET, PROJECT_URL, status=304, headers={"etag": ETAG_VALUE}) - result = build_transport().fetch_features( + result = build_api_client().fetch_features( ETAG_VALUE, options=PollingOptions(project_name=PROJECT_NAME) ) @@ -159,7 +159,7 @@ def test_fetch_features_sends_if_none_match_when_an_etag_is_known(build_transpor @responses.activate -def test_fetch_features_retries_and_recovers(build_transport): +def test_fetch_features_retries_and_recovers(build_api_client): responses.add(responses.GET, PROJECT_URL, json={}, status=500) responses.add( responses.GET, @@ -169,7 +169,7 @@ def test_fetch_features_retries_and_recovers(build_transport): headers={"etag": ETAG_VALUE}, ) - result = build_transport().fetch_features( + result = build_api_client().fetch_features( ETAG_VALUE, options=PollingOptions(project_name=PROJECT_NAME) ) @@ -179,14 +179,14 @@ def test_fetch_features_retries_and_recovers(build_transport): @responses.activate -def test_fetch_features_swallows_a_bad_custom_option(build_transport): +def test_fetch_features_swallows_a_bad_custom_option(build_api_client): responses.add( responses.GET, FULL_FEATURE_URL, json=MOCK_FEATURE_RESPONSE, status=200 ) - # `timeout` collides with the keyword the transport already passes. It is a + # `timeout` collides with the keyword the API client already passes. It is a # TypeError, not a RequestException, and fetch_features swallows everything. - result = build_transport(http=HttpOptions(options={"timeout": 5})).fetch_features( + result = build_api_client(http=HttpOptions(options={"timeout": 5})).fetch_features( options=PollingOptions() ) @@ -211,10 +211,10 @@ def test_fetch_features_swallows_a_bad_custom_option(build_transport): ), ), ) -def test_register(transport, payload, status, expected): +def test_register(api_client, payload, status, expected): responses.add(responses.POST, FULL_REGISTER_URL, **payload, status=status) - result = transport.register({"appName": APP_NAME}) + result = api_client.register({"appName": APP_NAME}) # No retry adapter on this one, unlike fetch_features. assert len(responses.calls) == 1 @@ -222,10 +222,10 @@ def test_register(transport, payload, status, expected): @responses.activate -def test_register_sends_the_payload_it_is_given(transport): +def test_register_sends_the_payload_it_is_given(api_client): responses.add(responses.POST, FULL_REGISTER_URL, json={}, status=202) - transport.register({"appName": APP_NAME, "strategies": []}) + api_client.register({"appName": APP_NAME, "strategies": []}) assert json.loads(responses.calls[0].request.body) == { "appName": APP_NAME, @@ -244,32 +244,32 @@ def skip_url_validation(monkeypatch): @responses.activate @pytest.mark.usefixtures("skip_url_validation") -def test_register_reraises_a_missing_schema(build_transport): - transport = build_transport(url="thisisnotavalidurl") +def test_register_reraises_a_missing_schema(build_api_client): + api_client = build_api_client(url="thisisnotavalidurl") # These four are re-raised rather than reported as False, so a url # requests rejects still fails initialize_client() loudly. with pytest.raises(MissingSchema): - transport.register({}) + api_client.register({}) @responses.activate @pytest.mark.usefixtures("skip_url_validation") -def test_register_reraises_an_invalid_schema(build_transport): - transport = build_transport(url="localhost:4242/api") +def test_register_reraises_an_invalid_schema(build_api_client): + api_client = build_api_client(url="localhost:4242/api") with pytest.raises(InvalidSchema): - transport.register({}) + api_client.register({}) @responses.activate -def test_register_lets_a_bad_custom_option_escape(build_transport): +def test_register_lets_a_bad_custom_option_escape(build_api_client): responses.add(responses.POST, FULL_REGISTER_URL, json={}, status=202) # A TypeError is not a RequestException, so unlike fetch_features this # propagates and fails initialize_client(). with pytest.raises(TypeError): - build_transport(http=HttpOptions(options={"timeout": 5})).register({}) + build_api_client(http=HttpOptions(options={"timeout": 5})).register({}) # send_metrics @@ -290,10 +290,10 @@ def test_register_lets_a_bad_custom_option_escape(build_transport): ), ), ) -def test_send_metrics(transport, payload, status, expected): +def test_send_metrics(api_client, payload, status, expected): responses.add(responses.POST, FULL_METRICS_URL, **payload, status=status) - result = transport.send_metrics(MOCK_METRICS_REQUEST) + result = api_client.send_metrics(MOCK_METRICS_REQUEST) request = json.loads(responses.calls[0].request.body) @@ -304,25 +304,25 @@ def test_send_metrics(transport, payload, status, expected): @responses.activate -def test_send_metrics_lets_a_bad_custom_option_escape(build_transport): +def test_send_metrics_lets_a_bad_custom_option_escape(build_api_client): responses.add(responses.POST, FULL_METRICS_URL, json={}, status=202) with pytest.raises(TypeError): - build_transport(http=HttpOptions(options={"timeout": 5})).send_metrics({}) + build_api_client(http=HttpOptions(options={"timeout": 5})).send_metrics({}) @responses.activate -def test_every_endpoint_gets_its_headers_from_the_factory(build_transport): +def test_every_endpoint_gets_its_headers_from_the_factory(build_api_client): responses.add( responses.GET, FULL_FEATURE_URL, json=MOCK_FEATURE_RESPONSE, status=200 ) responses.add(responses.POST, FULL_REGISTER_URL, json={}, status=202) responses.add(responses.POST, FULL_METRICS_URL, json={}, status=202) - transport = build_transport(metrics=MetricsOptions(interval=2)) + api_client = build_api_client(metrics=MetricsOptions(interval=2)) - transport.fetch_features(options=PollingOptions(interval=1)) - transport.register({}) - transport.send_metrics(MOCK_METRICS_REQUEST) + api_client.fetch_features(options=PollingOptions(interval=1)) + api_client.register({}) + api_client.send_metrics(MOCK_METRICS_REQUEST) fetch, register, metrics = (call.request.headers for call in responses.calls) # The custom header and the identification pair reach all three... @@ -371,40 +371,40 @@ def fetch_outcomes(events: RecordingEvents): ), ) def test_fetch_features_reports_how_the_fetch_ended( - build_transport, status, body, expected + build_api_client, status, body, expected ): responses.add(responses.GET, FULL_FEATURE_URL, json=body, status=status) events = RecordingEvents() - build_transport(events=events).fetch_features(options=PollingOptions()) + build_api_client(events=events).fetch_features(options=PollingOptions()) assert fetch_outcomes(events) == [expected] @responses.activate -def test_fetch_features_reports_an_empty_200_as_invalid(build_transport): +def test_fetch_features_reports_an_empty_200_as_invalid(build_api_client): responses.add(responses.GET, FULL_FEATURE_URL, body="", status=200) events = RecordingEvents() - build_transport(events=events).fetch_features(options=PollingOptions()) + build_api_client(events=events).fetch_features(options=PollingOptions()) assert fetch_outcomes(events) == [FetchStatus.INVALID_RESPONSE] @responses.activate def test_fetch_features_reports_unavailable_when_no_response_arrives( - build_transport, + build_api_client, ): responses.add(responses.GET, FULL_FEATURE_URL, body=ConnectionError("refused")) events = RecordingEvents() - build_transport(events=events).fetch_features(options=PollingOptions()) + build_api_client(events=events).fetch_features(options=PollingOptions()) assert fetch_outcomes(events) == [FetchStatus.UNAVAILABLE] @responses.activate -def test_fetch_features_survives_a_listener_that_raises(build_transport): +def test_fetch_features_survives_a_listener_that_raises(build_api_client): responses.add( responses.GET, FULL_FEATURE_URL, json=MOCK_FEATURE_RESPONSE, status=200 ) @@ -413,7 +413,7 @@ class BrokenEvents: def emit_event(self, event): raise RuntimeError("listener is broken") - result = build_transport(events=BrokenEvents()).fetch_features( + result = build_api_client(events=BrokenEvents()).fetch_features( options=PollingOptions() )