From f1d8d4e68880236973b9117532efa2bced1e8fb1 Mon Sep 17 00:00:00 2001 From: "m.kindritskiy" Date: Tue, 18 Jul 2023 16:22:13 +0300 Subject: [PATCH 1/2] allow to push defaults during flags preload --- .gitignore | 3 ++- client/featureflags/client/flags.py | 9 +++++++ .../featureflags/client/managers/asyncio.py | 23 ++++++++++++++--- client/featureflags/client/managers/sync.py | 25 +++++++++++++++---- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 9e0e41c..b3976d7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ examples/local_config.py .pytest_cache .lets .pdm.toml -__pypackages__ \ No newline at end of file +__pypackages__ +.DS_Store \ No newline at end of file diff --git a/client/featureflags/client/flags.py b/client/featureflags/client/flags.py index f0b35af..13715a7 100644 --- a/client/featureflags/client/flags.py +++ b/client/featureflags/client/flags.py @@ -95,6 +95,15 @@ def flush(self, delta=timedelta(minutes=1)): )) return stats + @staticmethod + def from_defaults(defaults): + interval_pb = Timestamp() + interval_pb.FromDatetime(datetime.utcnow()) + return [ + FlagUsage(name=name, interval=interval_pb, positive_count=0, negative_count=0) + for name in defaults + ] + class Tracer: """ diff --git a/client/featureflags/client/managers/asyncio.py b/client/featureflags/client/managers/asyncio.py index 79088ff..3a24715 100644 --- a/client/featureflags/client/managers/asyncio.py +++ b/client/featureflags/client/managers/asyncio.py @@ -66,8 +66,20 @@ def __init__(self, project, variables, channel, *, loop=None): stacklevel=2, ) - async def preload(self, *, timeout=None): - await self._exchange(timeout) + async def preload(self, *, timeout=None, defaults=None): + """ + Preload flags from the server. + :param timeout: timeout in seconds (for grpclib) + :param defaults: dict with default values for feature flags. + If passed, all feature flags will be synced with server, + otherwise flags will be synced only when they are accessed + for the first time. + """ + stats = None + if defaults is not None: + stats = self._stats.from_defaults(defaults) + + await self._exchange(timeout, stats) def start(self): if self._exchange_task is not None: @@ -105,8 +117,11 @@ async def _exchange_coro(self): await asyncio.sleep(interval) continue - async def _exchange(self, timeout): - request = self._state.get_request(self._stats.flush()) + async def _exchange(self, timeout, flags_usage=None): + if flags_usage is None: + flags_usage = self._stats.flush() + + request = self._state.get_request(flags_usage) log.debug('Exchange request, project: %r, version: %r, stats: %r', request.project, request.version, request.flags_usage) reply = await self._stub.Exchange(request, timeout=timeout) diff --git a/client/featureflags/client/managers/sync.py b/client/featureflags/client/managers/sync.py index 2f6dfb9..6cb688d 100644 --- a/client/featureflags/client/managers/sync.py +++ b/client/featureflags/client/managers/sync.py @@ -46,11 +46,26 @@ def __init__(self, project, variables, channel): self._int_gen.send(None) self._next_exchange = datetime.utcnow() - def preload(self, timeout=None): - self._exchange(timeout) - - def _exchange(self, timeout): - request = self._state.get_request(self._stats.flush()) + def preload(self, timeout=None, defaults=None): + """ + Preload flags from the server. + :param timeout: timeout in seconds (for grpcio) + :param defaults: dict with default values for feature flags. + If passed, all feature flags will be synced with server, + otherwise flags will be synced only when they are accessed + for the first time. + """ + stats = None + if defaults is not None: + stats = self._stats.from_defaults(defaults) + + self._exchange(timeout, stats) + + def _exchange(self, timeout, flags_usage=None): + if flags_usage is None: + flags_usage = self._stats.flush() + + request = self._state.get_request(flags_usage) log.debug('Exchange request, project: %r, version: %r, stats: %r', request.project, request.version, request.flags_usage) reply = self._stub.Exchange(request, timeout=timeout) From a4fd06062e409fe261f41261ae681253b6f45c47 Mon Sep 17 00:00:00 2001 From: "m.kindritskiy" Date: Tue, 18 Jul 2023 16:37:18 +0300 Subject: [PATCH 2/2] update docs --- README.rst | 15 +++++++++++++++ client/featureflags/client/flags.py | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/README.rst b/README.rst index 4f4e991..531f515 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,21 @@ Server consists of two services: - grpclib_ + hiku_ +Installation +~~~~~~~~~~~~ + +To install client library for synchronous app: + +.. code-block:: shell + + $ pip install featureflags-client grpcio + +To install client library for asynchronous app: + +.. code-block:: shell + + $ pip install featureflags-client grpclib + Development ~~~~~~~~~~~ diff --git a/client/featureflags/client/flags.py b/client/featureflags/client/flags.py index 13715a7..e47525e 100644 --- a/client/featureflags/client/flags.py +++ b/client/featureflags/client/flags.py @@ -225,3 +225,12 @@ def flags( finally: if tracer is not None: self._manager.add_trace(tracer) + + def preload(self, timeout=None): + """Preload flags from server. + This method syncs all flags with server""" + self._manager.preload(timeout=timeout, defaults=self._defaults) + + async def preload_async(self, timeout=None): + """Async version of `preload` method""" + await self._manager.preload(timeout=timeout, defaults=self._defaults)