growthbook 1.4.3__py2.py3-none-any.whl → 1.4.4__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
growthbook/__init__.py CHANGED
@@ -18,5 +18,5 @@ from .plugins import (
18
18
  )
19
19
 
20
20
  # x-release-please-start-version
21
- __version__ = "1.4.3"
21
+ __version__ = "1.4.4"
22
22
  # x-release-please-end
growthbook/growthbook.py CHANGED
@@ -100,7 +100,8 @@ class InMemoryFeatureCache(AbstractFeatureCache):
100
100
  def set(self, key: str, value: Dict, ttl: int) -> None:
101
101
  if key in self.cache:
102
102
  self.cache[key].update(value)
103
- self.cache[key] = CacheEntry(value, ttl)
103
+ else:
104
+ self.cache[key] = CacheEntry(value, ttl)
104
105
 
105
106
  def clear(self) -> None:
106
107
  self.cache.clear()
@@ -327,6 +328,11 @@ class FeatureRepository(object):
327
328
  self.http: Optional[PoolManager] = None
328
329
  self.sse_client: Optional[SSEClient] = None
329
330
  self._feature_update_callbacks: List[Callable[[Dict], None]] = []
331
+
332
+ # Background refresh support
333
+ self._refresh_thread: Optional[threading.Thread] = None
334
+ self._refresh_stop_event = threading.Event()
335
+ self._refresh_lock = threading.Lock()
330
336
 
331
337
  def set_cache(self, cache: AbstractFeatureCache) -> None:
332
338
  self.cache = cache
@@ -375,6 +381,7 @@ class FeatureRepository(object):
375
381
  return res
376
382
  return cached
377
383
 
384
+
378
385
  async def load_features_async(
379
386
  self, api_host: str, client_key: str, decryption_key: str = "", ttl: int = 600
380
387
  ) -> Optional[Dict]:
@@ -493,6 +500,52 @@ class FeatureRepository(object):
493
500
  if self.sse_client:
494
501
  self.sse_client.disconnect(timeout=timeout)
495
502
  self.sse_client = None
503
+
504
+ def start_background_refresh(self, api_host: str, client_key: str, decryption_key: str, ttl: int = 600, refresh_interval: int = 300) -> None:
505
+ """Start periodic background refresh task"""
506
+ with self._refresh_lock:
507
+ if self._refresh_thread is not None:
508
+ return # Already running
509
+
510
+ self._refresh_stop_event.clear()
511
+ self._refresh_thread = threading.Thread(
512
+ target=self._background_refresh_worker,
513
+ args=(api_host, client_key, decryption_key, ttl, refresh_interval),
514
+ daemon=True
515
+ )
516
+ self._refresh_thread.start()
517
+ logger.debug("Started background refresh task")
518
+
519
+ def _background_refresh_worker(self, api_host: str, client_key: str, decryption_key: str, ttl: int, refresh_interval: int) -> None:
520
+ """Worker method for periodic background refresh"""
521
+ while not self._refresh_stop_event.is_set():
522
+ try:
523
+ # Wait for the refresh interval or stop event
524
+ if self._refresh_stop_event.wait(refresh_interval):
525
+ break # Stop event was set
526
+
527
+ logger.debug("Background refresh for Features - started")
528
+ res = self._fetch_features(api_host, client_key, decryption_key)
529
+ if res is not None:
530
+ cache_key = api_host + "::" + client_key
531
+ self.cache.set(cache_key, res, ttl)
532
+ logger.debug("Background refresh completed")
533
+ # Notify callbacks about fresh features
534
+ self._notify_feature_update_callbacks(res)
535
+ else:
536
+ logger.warning("Background refresh failed")
537
+ except Exception as e:
538
+ logger.warning(f"Background refresh error: {e}")
539
+
540
+ def stop_background_refresh(self) -> None:
541
+ """Stop background refresh task"""
542
+ self._refresh_stop_event.set()
543
+
544
+ with self._refresh_lock:
545
+ if self._refresh_thread is not None:
546
+ self._refresh_thread.join(timeout=1.0) # Wait up to 1 second
547
+ self._refresh_thread = None
548
+ logger.debug("Stopped background refresh task")
496
549
 
497
550
  @staticmethod
498
551
  def _get_features_url(api_host: str, client_key: str) -> str:
@@ -522,6 +575,8 @@ class GrowthBook(object):
522
575
  savedGroups: dict = {},
523
576
  streaming: bool = False,
524
577
  streaming_connection_timeout: int = 30,
578
+ stale_while_revalidate: bool = False,
579
+ stale_ttl: int = 300, # 5 minutes default
525
580
  plugins: List = None,
526
581
  # Deprecated args
527
582
  trackingCallback=None,
@@ -551,6 +606,8 @@ class GrowthBook(object):
551
606
 
552
607
  self._streaming = streaming
553
608
  self._streaming_timeout = streaming_connection_timeout
609
+ self._stale_while_revalidate = stale_while_revalidate
610
+ self._stale_ttl = stale_ttl
554
611
 
555
612
  # Deprecated args
556
613
  self._user = user
@@ -603,6 +660,13 @@ class GrowthBook(object):
603
660
  if self._streaming:
604
661
  self.load_features()
605
662
  self.startAutoRefresh()
663
+ elif self._stale_while_revalidate and self._client_key:
664
+ # Start background refresh task for stale-while-revalidate
665
+ self.load_features() # Initial load
666
+ feature_repo.start_background_refresh(
667
+ self._api_host, self._client_key, self._decryption_key,
668
+ self._cache_ttl, self._stale_ttl
669
+ )
606
670
 
607
671
  def _on_feature_update(self, features_data: Dict) -> None:
608
672
  """Callback to handle automatic feature updates from FeatureRepository"""
@@ -739,6 +803,13 @@ class GrowthBook(object):
739
803
  except Exception as e:
740
804
  logger.warning(f"Error stopping auto refresh during destroy: {e}")
741
805
 
806
+ try:
807
+ # Stop background refresh operations
808
+ if self._stale_while_revalidate and self._client_key:
809
+ feature_repo.stop_background_refresh()
810
+ except Exception as e:
811
+ logger.warning(f"Error stopping background refresh during destroy: {e}")
812
+
742
813
  try:
743
814
  # Clean up feature update callback
744
815
  if self._client_key:
@@ -790,8 +861,8 @@ class GrowthBook(object):
790
861
  def _ensure_fresh_features(self) -> None:
791
862
  """Lazy refresh: Check cache expiry and refresh if needed, but only if client_key is provided"""
792
863
 
793
- if self._streaming or not self._client_key:
794
- return # Skip cache checks - SSE handles freshness for streaming users
864
+ if self._streaming or self._stale_while_revalidate or not self._client_key:
865
+ return # Skip cache checks - SSE or background refresh handles freshness
795
866
 
796
867
  try:
797
868
  self.load_features()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: growthbook
3
- Version: 1.4.3
3
+ Version: 1.4.4
4
4
  Summary: Powerful Feature flagging and A/B testing for Python apps
5
5
  Home-page: https://github.com/growthbook/growthbook-python
6
6
  Author: GrowthBook
@@ -1,15 +1,15 @@
1
- growthbook/__init__.py,sha256=Js_KujzX2ehccclPD8fRM8duCGQdwiOyyV5uNdWCKf8,444
1
+ growthbook/__init__.py,sha256=sEc27BZrhkkInXWijTKKmrZ4kfeyR3z8V7SFG7ykvM0,444
2
2
  growthbook/common_types.py,sha256=OUGkqoUuYetWz1cyA1eWz5DM3awYw_ExcNAjFqJuGAc,14881
3
3
  growthbook/core.py,sha256=n9nwna26iZTY48LIvQqu5N_RrE35X0wlRBhq0-Qdb-s,35241
4
- growthbook/growthbook.py,sha256=EK7nkS0Rbkc0aDORKh_0ie6cc7hXlJUui5ve-GkWmHk,35996
4
+ growthbook/growthbook.py,sha256=E4CrSOtrbnYKfuC-_7NAsZwqtRnRy7h8j9MsRestXMI,39417
5
5
  growthbook/growthbook_client.py,sha256=1bDIuJoxlKUR_bKe_gD6V7JlUPt53uGgix9DhgSkPPc,23360
6
6
  growthbook/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  growthbook/plugins/__init__.py,sha256=y2eAV1sA041XWcftBVTDH0t-ggy9r2C5oKRYRF6XR6s,602
8
8
  growthbook/plugins/base.py,sha256=PWBXUBj62hi25Y5Eif9WmEWagWdkwGXHi2dMtn44bo8,3637
9
9
  growthbook/plugins/growthbook_tracking.py,sha256=FvPFOuKF_xKjmTX8x_hzMlHrrL-68Y2ZPw1Hfl2_ilQ,11333
10
10
  growthbook/plugins/request_context.py,sha256=O5FJDrjJR5u0rx3ENGO9cOsKMHd9e0l0Nvdb1PHfmm8,12951
11
- growthbook-1.4.3.dist-info/licenses/LICENSE,sha256=D-TcBckB0dTPUlNJ8jBiTIJIj1ekHLB1CY7HJtJKhMY,1069
12
- growthbook-1.4.3.dist-info/METADATA,sha256=l5wX61QF36lMGmPSxZsr_CKmHOdqOU9PKAbQPA3gN50,22074
13
- growthbook-1.4.3.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
14
- growthbook-1.4.3.dist-info/top_level.txt,sha256=dzfRQFGYejCIUstRSrrRVTMlxf7pBqASTI5S8gGRlXw,11
15
- growthbook-1.4.3.dist-info/RECORD,,
11
+ growthbook-1.4.4.dist-info/licenses/LICENSE,sha256=D-TcBckB0dTPUlNJ8jBiTIJIj1ekHLB1CY7HJtJKhMY,1069
12
+ growthbook-1.4.4.dist-info/METADATA,sha256=961Hf5woy1DJDd1hKGl7maQLAGUv_A2pvEM32l1GKZs,22074
13
+ growthbook-1.4.4.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
14
+ growthbook-1.4.4.dist-info/top_level.txt,sha256=dzfRQFGYejCIUstRSrrRVTMlxf7pBqASTI5S8gGRlXw,11
15
+ growthbook-1.4.4.dist-info/RECORD,,