| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| """ | ||
| Default implementation of feature flag polling requests. | ||
| """ | ||
|
|
||
| import json | ||
| from collections import namedtuple | ||
| from typing import Optional | ||
| from urllib import parse | ||
|
|
||
| from ldclient.impl.aio.transport import AsyncHTTPTransport | ||
| from ldclient.impl.datasource.datasource_common import FDV1_POLLING_ENDPOINT | ||
| from ldclient.impl.util import _headers, log, throw_if_unsuccessful_response | ||
| from ldclient.interfaces import AsyncFeatureRequester | ||
| from ldclient.versioned_data_kind import FEATURES, SEGMENTS | ||
|
|
||
| CacheEntry = namedtuple('CacheEntry', ['data', 'etag']) | ||
|
|
||
|
|
||
| class AsyncFeatureRequesterImpl(AsyncFeatureRequester): | ||
| def __init__(self, config, transport: Optional[AsyncHTTPTransport] = None): | ||
| self._cache: dict = dict() | ||
| # Only close the transport on shutdown if we created it; an injected | ||
| # transport is owned by the caller. | ||
| self._owns_transport = transport is None | ||
| self._transport = transport if transport is not None else AsyncHTTPTransport(config) | ||
| self._config = config | ||
| self._poll_uri = config.base_uri + FDV1_POLLING_ENDPOINT | ||
| if config.payload_filter_key is not None: | ||
| self._poll_uri += '?%s' % parse.urlencode({'filter': config.payload_filter_key}) | ||
|
|
||
| async def get_all_data(self): | ||
| uri = self._poll_uri | ||
| hdrs = _headers(self._config) | ||
| cache_entry = self._cache.get(uri) | ||
| hdrs['Accept-Encoding'] = 'gzip' | ||
| if cache_entry is not None: | ||
| hdrs['If-None-Match'] = cache_entry.etag | ||
| r = await self._transport.request('GET', uri, headers=hdrs) | ||
| throw_if_unsuccessful_response(r) | ||
| if r.status == 304 and cache_entry is not None: | ||
| data = cache_entry.data | ||
| etag = cache_entry.etag | ||
| from_cache = True | ||
| else: | ||
| data = json.loads(r.body) | ||
| etag = r.headers.get('ETag') | ||
| from_cache = False | ||
| if etag is not None: | ||
| self._cache[uri] = CacheEntry(data=data, etag=etag) | ||
| log.debug("%s response status:[%d] From cache? [%s] ETag:[%s]", uri, r.status, from_cache, etag) | ||
|
|
||
| return {FEATURES: data['flags'], SEGMENTS: data['segments']} | ||
|
Comment thread
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| async def close(self): | ||
| if self._owns_transport: | ||
| await self._transport.close() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| """ | ||
| Default implementation of the polling component. | ||
| """ | ||
|
|
||
| # currently excluded from documentation - see docs/README.md | ||
|
|
||
| import time | ||
| from typing import Optional | ||
|
|
||
| from ldclient.async_config import AsyncConfig | ||
| from ldclient.impl.aio.concurrency import AsyncEvent, AsyncRepeatingTask | ||
| from ldclient.impl.datasource.datasource_common import sink_or_store | ||
| from ldclient.impl.util import ( | ||
| UnsuccessfulResponseException, | ||
| http_error_message, | ||
| is_http_error_recoverable, | ||
| log | ||
| ) | ||
| from ldclient.interfaces import ( | ||
| AsyncFeatureRequester, | ||
| AsyncFeatureStore, | ||
| AsyncUpdateProcessor, | ||
| DataSourceErrorInfo, | ||
| DataSourceErrorKind, | ||
| DataSourceState | ||
| ) | ||
|
|
||
|
|
||
| class AsyncPollingUpdateProcessor(AsyncUpdateProcessor): | ||
| def __init__(self, config: AsyncConfig, requester: AsyncFeatureRequester, store: AsyncFeatureStore, ready: AsyncEvent): | ||
| self._config = config | ||
| self._data_source_update_sink = config.data_source_update_sink | ||
| self._requester = requester | ||
| self._store = store | ||
| self._ready = ready | ||
| self._task = AsyncRepeatingTask("ldclient.datasource.polling", config.poll_interval, 0, self._fetch_and_store) | ||
|
|
||
| def start(self): | ||
| log.info("Starting AsyncPollingUpdateProcessor with request interval: " + str(self._config.poll_interval)) | ||
| self._task.start() | ||
|
|
||
| def initialized(self): | ||
| return self._ready.is_set() and self._store.initialized | ||
|
|
||
| async def stop(self): | ||
| self.__stop_with_error_info(None) | ||
| # Wait for the current poll to finish before closing the transport, so we do | ||
| # not close it while a request is still using it. The close is in a finally | ||
| # so an owned transport is still released if stop() is cancelled mid-wait. | ||
| try: | ||
| await self._task.wait_stopped() | ||
| finally: | ||
| await self._requester.close() | ||
|
|
||
| def __stop_with_error_info(self, error: Optional[DataSourceErrorInfo]): | ||
| log.info("Stopping AsyncPollingUpdateProcessor") | ||
| self._task.stop() | ||
|
|
||
| if self._data_source_update_sink is None: | ||
| return | ||
|
|
||
| self._data_source_update_sink.update_status(DataSourceState.OFF, error) | ||
|
|
||
| async def _fetch_and_store(self): | ||
| try: | ||
| all_data = await self._requester.get_all_data() | ||
| await sink_or_store(self._data_source_update_sink, self._store).init(all_data) | ||
| if not self._ready.is_set() and self._store.initialized: | ||
| log.info("AsyncPollingUpdateProcessor initialized ok") | ||
| self._ready.set() | ||
|
|
||
| if self._data_source_update_sink is not None: | ||
| self._data_source_update_sink.update_status(DataSourceState.VALID, None) | ||
|
Comment thread
cursor[bot] marked this conversation as resolved.
|
||
| except UnsuccessfulResponseException as e: | ||
| error_info = DataSourceErrorInfo(DataSourceErrorKind.ERROR_RESPONSE, e.status, time.time(), str(e)) | ||
|
|
||
| http_error_message_result = http_error_message(e.status, "polling request") | ||
| if not is_http_error_recoverable(e.status): | ||
| log.error(http_error_message_result) | ||
| self._ready.set() # if client is initializing, make it stop waiting; has no effect if already inited | ||
| self.__stop_with_error_info(error_info) | ||
| else: | ||
| log.warning(http_error_message_result) | ||
|
|
||
| if self._data_source_update_sink is not None: | ||
| self._data_source_update_sink.update_status(DataSourceState.INTERRUPTED, error_info) | ||
| except Exception as e: | ||
| log.exception('Error: Exception encountered when updating flags. %s' % e) | ||
| if self._data_source_update_sink is not None: | ||
| self._data_source_update_sink.update_status(DataSourceState.INTERRUPTED, DataSourceErrorInfo(DataSourceErrorKind.UNKNOWN, 0, time.time(), str(e))) | ||
| Back | FazBrowse Home | New Git URL |
Uh oh!
There was an error while loading. Please reload this page.