growthbook 1.4.5__py2.py3-none-any.whl → 1.4.6__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.5"
21
+ __version__ = "1.4.6"
22
22
  # x-release-please-end
@@ -426,7 +426,7 @@ class Options:
426
426
  sticky_bucket_service: Optional[AbstractStickyBucketService] = None
427
427
  sticky_bucket_identifier_attributes: Optional[List[str]] = None
428
428
  on_experiment_viewed: Optional[Callable[[Experiment, Result, Optional[UserContext]], None]] = None
429
- on_feature_usage: Optional[Callable[[str, 'FeatureResult'], None]] = None
429
+ on_feature_usage: Optional[Callable[[str, 'FeatureResult', UserContext], None]] = None
430
430
  tracking_plugins: Optional[List[Any]] = None
431
431
 
432
432
 
growthbook/growthbook.py CHANGED
@@ -503,6 +503,10 @@ class FeatureRepository(object):
503
503
 
504
504
  def start_background_refresh(self, api_host: str, client_key: str, decryption_key: str, ttl: int = 600, refresh_interval: int = 300) -> None:
505
505
  """Start periodic background refresh task"""
506
+
507
+ if not client_key:
508
+ raise ValueError("Must specify `client_key` to refresh features")
509
+
506
510
  with self._refresh_lock:
507
511
  if self._refresh_thread is not None:
508
512
  return # Already running
@@ -620,6 +624,7 @@ class GrowthBook(object):
620
624
  self._tracked: Dict[str, Any] = {}
621
625
  self._assigned: Dict[str, Any] = {}
622
626
  self._subscriptions: Set[Any] = set()
627
+ self._is_updating_features = False
623
628
 
624
629
  # support plugins
625
630
  self._plugins: List = plugins or []
@@ -662,7 +667,7 @@ class GrowthBook(object):
662
667
  if self._streaming:
663
668
  self.load_features()
664
669
  self.startAutoRefresh()
665
- elif self._stale_while_revalidate and self._client_key:
670
+ elif self._stale_while_revalidate:
666
671
  # Start background refresh task for stale-while-revalidate
667
672
  self.load_features() # Initial load
668
673
  feature_repo.start_background_refresh(
@@ -752,19 +757,24 @@ class GrowthBook(object):
752
757
  return self.set_features(features)
753
758
 
754
759
  def set_features(self, features: dict) -> None:
755
- self._features = {}
756
- for key, feature in features.items():
757
- if isinstance(feature, Feature):
758
- self._features[key] = feature
759
- else:
760
- self._features[key] = Feature(
761
- rules=feature.get("rules", []),
762
- defaultValue=feature.get("defaultValue", None),
763
- )
764
- # Update the global context with the new features and saved groups
765
- self._global_ctx.features = self._features
766
- self._global_ctx.saved_groups = self._saved_groups
767
- self.refresh_sticky_buckets()
760
+ # Prevent infinite recursion during feature updates
761
+ self._is_updating_features = True
762
+ try:
763
+ self._features = {}
764
+ for key, feature in features.items():
765
+ if isinstance(feature, Feature):
766
+ self._features[key] = feature
767
+ else:
768
+ self._features[key] = Feature(
769
+ rules=feature.get("rules", []),
770
+ defaultValue=feature.get("defaultValue", None),
771
+ )
772
+ # Update the global context with the new features and saved groups
773
+ self._global_ctx.features = self._features
774
+ self._global_ctx.saved_groups = self._saved_groups
775
+ self.refresh_sticky_buckets()
776
+ finally:
777
+ self._is_updating_features = False
768
778
 
769
779
  # @deprecated, use get_features
770
780
  def getFeatures(self) -> Dict[str, Feature]:
@@ -864,6 +874,10 @@ class GrowthBook(object):
864
874
  def _ensure_fresh_features(self) -> None:
865
875
  """Lazy refresh: Check cache expiry and refresh if needed, but only if client_key is provided"""
866
876
 
877
+ # Prevent infinite recursion when updating features (e.g., during sticky bucket refresh)
878
+ if self._is_updating_features:
879
+ return
880
+
867
881
  if self._streaming or self._stale_while_revalidate or not self._client_key:
868
882
  return # Skip cache checks - SSE or background refresh handles freshness
869
883
 
@@ -897,7 +911,7 @@ class GrowthBook(object):
897
911
  # Call feature usage callback if provided
898
912
  if self._featureUsageCallback:
899
913
  try:
900
- self._featureUsageCallback(key, result)
914
+ self._featureUsageCallback(key, result, self._user_ctx)
901
915
  except Exception:
902
916
  pass
903
917
  return result
@@ -503,7 +503,7 @@ class GrowthBookClient:
503
503
  # Call feature usage callback if provided
504
504
  if self.options.on_feature_usage:
505
505
  try:
506
- self.options.on_feature_usage(key, result)
506
+ self.options.on_feature_usage(key, result, user_context)
507
507
  except Exception:
508
508
  logger.exception("Error in feature usage callback")
509
509
  return result
@@ -516,7 +516,7 @@ class GrowthBookClient:
516
516
  # Call feature usage callback if provided
517
517
  if self.options.on_feature_usage:
518
518
  try:
519
- self.options.on_feature_usage(key, result)
519
+ self.options.on_feature_usage(key, result, user_context)
520
520
  except Exception:
521
521
  logger.exception("Error in feature usage callback")
522
522
  return result.on
@@ -529,7 +529,7 @@ class GrowthBookClient:
529
529
  # Call feature usage callback if provided
530
530
  if self.options.on_feature_usage:
531
531
  try:
532
- self.options.on_feature_usage(key, result)
532
+ self.options.on_feature_usage(key, result, user_context)
533
533
  except Exception:
534
534
  logger.exception("Error in feature usage callback")
535
535
  return result.off
@@ -541,7 +541,7 @@ class GrowthBookClient:
541
541
  # Call feature usage callback if provided
542
542
  if self.options.on_feature_usage:
543
543
  try:
544
- self.options.on_feature_usage(key, result)
544
+ self.options.on_feature_usage(key, result, user_context)
545
545
  except Exception:
546
546
  logger.exception("Error in feature usage callback")
547
547
  return result.value if result.value is not None else fallback
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: growthbook
3
- Version: 1.4.5
3
+ Version: 1.4.6
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
@@ -499,7 +499,6 @@ class MyStickyBucketService(AbstractStickyBucketService):
499
499
  })
500
500
 
501
501
  # Pass in an instance of this service to your GrowthBook constructor
502
-
503
502
  gb = GrowthBook(
504
503
  sticky_bucket_service = MyStickyBucketService()
505
504
  )
@@ -0,0 +1,15 @@
1
+ growthbook/__init__.py,sha256=1B2U7uOmsNvvEQ3DQ2hLuvALy6pqnJAduUUIXFbFbso,444
2
+ growthbook/common_types.py,sha256=OMfssahxLjvCGuGsWa75G6JMmu5xH1hVrK0pCL1ArMU,14972
3
+ growthbook/core.py,sha256=n9nwna26iZTY48LIvQqu5N_RrE35X0wlRBhq0-Qdb-s,35241
4
+ growthbook/growthbook.py,sha256=8U6UmxHAdLDCsUi3CBFTvK4nBchs9-ukXwd_bZAiXsY,40333
5
+ growthbook/growthbook_client.py,sha256=igD6T9MP9rfYzS1Dk0wBJ3fgfNzJxDQZ_bm5SS8XO7I,24632
6
+ growthbook/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ growthbook/plugins/__init__.py,sha256=y2eAV1sA041XWcftBVTDH0t-ggy9r2C5oKRYRF6XR6s,602
8
+ growthbook/plugins/base.py,sha256=PWBXUBj62hi25Y5Eif9WmEWagWdkwGXHi2dMtn44bo8,3637
9
+ growthbook/plugins/growthbook_tracking.py,sha256=FvPFOuKF_xKjmTX8x_hzMlHrrL-68Y2ZPw1Hfl2_ilQ,11333
10
+ growthbook/plugins/request_context.py,sha256=O5FJDrjJR5u0rx3ENGO9cOsKMHd9e0l0Nvdb1PHfmm8,12951
11
+ growthbook-1.4.6.dist-info/licenses/LICENSE,sha256=D-TcBckB0dTPUlNJ8jBiTIJIj1ekHLB1CY7HJtJKhMY,1069
12
+ growthbook-1.4.6.dist-info/METADATA,sha256=q04J95e-Kgf9yssVQCfG_JJZHGHWsTDndDwZm1c74eg,22073
13
+ growthbook-1.4.6.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
14
+ growthbook-1.4.6.dist-info/top_level.txt,sha256=dzfRQFGYejCIUstRSrrRVTMlxf7pBqASTI5S8gGRlXw,11
15
+ growthbook-1.4.6.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- growthbook/__init__.py,sha256=JLdue4592q3iH1ck1qG9Fbv-p8i07SJo-tUJhGCf60s,444
2
- growthbook/common_types.py,sha256=KYA9rmWRMde2JnsUjygsiaJ1q-KZakDdzPAtUOnrKyY,14959
3
- growthbook/core.py,sha256=n9nwna26iZTY48LIvQqu5N_RrE35X0wlRBhq0-Qdb-s,35241
4
- growthbook/growthbook.py,sha256=xjuX-q8oPsLqP0kWBZqy7LpoTkrgjMZCOWjtpUsQR1o,39793
5
- growthbook/growthbook_client.py,sha256=kfdc2NGhdmsXxaVIm0CBlNCMkwicfPpbDZ10nZSPd7w,24576
6
- growthbook/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- growthbook/plugins/__init__.py,sha256=y2eAV1sA041XWcftBVTDH0t-ggy9r2C5oKRYRF6XR6s,602
8
- growthbook/plugins/base.py,sha256=PWBXUBj62hi25Y5Eif9WmEWagWdkwGXHi2dMtn44bo8,3637
9
- growthbook/plugins/growthbook_tracking.py,sha256=FvPFOuKF_xKjmTX8x_hzMlHrrL-68Y2ZPw1Hfl2_ilQ,11333
10
- growthbook/plugins/request_context.py,sha256=O5FJDrjJR5u0rx3ENGO9cOsKMHd9e0l0Nvdb1PHfmm8,12951
11
- growthbook-1.4.5.dist-info/licenses/LICENSE,sha256=D-TcBckB0dTPUlNJ8jBiTIJIj1ekHLB1CY7HJtJKhMY,1069
12
- growthbook-1.4.5.dist-info/METADATA,sha256=8KBWZ908qYYku0GqZCeZ1KNS5YLPxs7FXZVl6wesLWM,22074
13
- growthbook-1.4.5.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
14
- growthbook-1.4.5.dist-info/top_level.txt,sha256=dzfRQFGYejCIUstRSrrRVTMlxf7pBqASTI5S8gGRlXw,11
15
- growthbook-1.4.5.dist-info/RECORD,,