growthbook 1.3.1__py2.py3-none-any.whl → 1.4.0__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 +1 -1
- growthbook/common_types.py +13 -11
- growthbook/core.py +4 -4
- growthbook/growthbook.py +3 -3
- growthbook/growthbook_client.py +47 -7
- growthbook/plugins/growthbook_tracking.py +37 -14
- growthbook/plugins/request_context.py +22 -6
- {growthbook-1.3.1.dist-info → growthbook-1.4.0.dist-info}/METADATA +1 -1
- growthbook-1.4.0.dist-info/RECORD +15 -0
- growthbook-1.3.1.dist-info/RECORD +0 -15
- {growthbook-1.3.1.dist-info → growthbook-1.4.0.dist-info}/WHEEL +0 -0
- {growthbook-1.3.1.dist-info → growthbook-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {growthbook-1.3.1.dist-info → growthbook-1.4.0.dist-info}/top_level.txt +0 -0
growthbook/__init__.py
CHANGED
growthbook/common_types.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
+
from token import OP
|
|
4
5
|
# Only require typing_extensions if using Python 3.7 or earlier
|
|
5
6
|
if sys.version_info >= (3, 8):
|
|
6
7
|
from typing import TypedDict
|
|
@@ -8,7 +9,7 @@ else:
|
|
|
8
9
|
from typing_extensions import TypedDict
|
|
9
10
|
|
|
10
11
|
from dataclasses import dataclass, field
|
|
11
|
-
from typing import Any, Dict, List, Optional, Union, Set, Tuple
|
|
12
|
+
from typing import Any, Callable, Dict, List, Optional, Union, Set, Tuple
|
|
12
13
|
from enum import Enum
|
|
13
14
|
from abc import ABC, abstractmethod
|
|
14
15
|
|
|
@@ -400,6 +401,15 @@ class StackContext:
|
|
|
400
401
|
class FeatureRefreshStrategy(Enum):
|
|
401
402
|
STALE_WHILE_REVALIDATE = 'HTTP_REFRESH'
|
|
402
403
|
SERVER_SENT_EVENTS = 'SSE'
|
|
404
|
+
@dataclass
|
|
405
|
+
class UserContext:
|
|
406
|
+
# user_id: Optional[str] = None
|
|
407
|
+
url: str = ""
|
|
408
|
+
attributes: Dict[str, Any] = field(default_factory=dict)
|
|
409
|
+
groups: Dict[str, str] = field(default_factory=dict)
|
|
410
|
+
forced_variations: Dict[str, Any] = field(default_factory=dict)
|
|
411
|
+
overrides: Dict[str, Any] = field(default_factory=dict)
|
|
412
|
+
sticky_bucket_assignment_docs: Dict[str, Any] = field(default_factory=dict)
|
|
403
413
|
|
|
404
414
|
@dataclass
|
|
405
415
|
class Options:
|
|
@@ -415,17 +425,9 @@ class Options:
|
|
|
415
425
|
refresh_strategy: Optional[FeatureRefreshStrategy] = FeatureRefreshStrategy.STALE_WHILE_REVALIDATE
|
|
416
426
|
sticky_bucket_service: Optional[AbstractStickyBucketService] = None
|
|
417
427
|
sticky_bucket_identifier_attributes: Optional[List[str]] = None
|
|
418
|
-
on_experiment_viewed=None
|
|
428
|
+
on_experiment_viewed: Optional[Callable[[Experiment, Result, Optional[UserContext]], None]] = None
|
|
429
|
+
tracking_plugins: Optional[List[Any]] = None
|
|
419
430
|
|
|
420
|
-
@dataclass
|
|
421
|
-
class UserContext:
|
|
422
|
-
# user_id: Optional[str] = None
|
|
423
|
-
url: str = ""
|
|
424
|
-
attributes: Dict[str, Any] = field(default_factory=dict)
|
|
425
|
-
groups: Dict[str, str] = field(default_factory=dict)
|
|
426
|
-
forced_variations: Dict[str, Any] = field(default_factory=dict)
|
|
427
|
-
overrides: Dict[str, Any] = field(default_factory=dict)
|
|
428
|
-
sticky_bucket_assignment_docs: Dict[str, Any] = field(default_factory=dict)
|
|
429
431
|
|
|
430
432
|
@dataclass
|
|
431
433
|
class GlobalContext:
|
growthbook/core.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
|
|
5
5
|
from urllib.parse import urlparse, parse_qs
|
|
6
6
|
from typing import Callable, Optional, Any, Set, Tuple, List, Dict
|
|
7
|
-
from .common_types import EvaluationContext, FeatureResult, Experiment, Filter, Result, VariationMeta
|
|
7
|
+
from .common_types import EvaluationContext, FeatureResult, Experiment, Filter, Result, UserContext, VariationMeta
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger("growthbook.core")
|
|
@@ -414,7 +414,7 @@ def eval_feature(
|
|
|
414
414
|
key: str,
|
|
415
415
|
evalContext: EvaluationContext = None,
|
|
416
416
|
callback_subscription: Callable[[Experiment, Result], None] = None,
|
|
417
|
-
tracking_cb: Callable[[Experiment, Result], None] = None
|
|
417
|
+
tracking_cb: Callable[[Experiment, Result, UserContext], None] = None
|
|
418
418
|
) -> FeatureResult:
|
|
419
419
|
"""Core feature evaluation logic as a standalone function"""
|
|
420
420
|
|
|
@@ -634,7 +634,7 @@ def _get_sticky_bucket_variation(
|
|
|
634
634
|
def run_experiment(experiment: Experiment,
|
|
635
635
|
featureId: Optional[str] = None,
|
|
636
636
|
evalContext: EvaluationContext = None,
|
|
637
|
-
tracking_cb: Callable[[Experiment, Result], None] = None
|
|
637
|
+
tracking_cb: Callable[[Experiment, Result, UserContext], None] = None
|
|
638
638
|
) -> Result:
|
|
639
639
|
if evalContext is None:
|
|
640
640
|
raise ValueError("evalContext is required - run_experiment")
|
|
@@ -859,7 +859,7 @@ def run_experiment(experiment: Experiment,
|
|
|
859
859
|
|
|
860
860
|
# 14. Fire the tracking callback if set
|
|
861
861
|
if tracking_cb:
|
|
862
|
-
tracking_cb(experiment, result)
|
|
862
|
+
tracking_cb(experiment, result, evalContext.user)
|
|
863
863
|
|
|
864
864
|
# 15. Return the result
|
|
865
865
|
logger.debug("Assigned variation %d in experiment %s", assigned, experiment.key)
|
growthbook/growthbook.py
CHANGED
|
@@ -671,7 +671,7 @@ class GrowthBook(object):
|
|
|
671
671
|
return self.get_feature_value(key, fallback)
|
|
672
672
|
|
|
673
673
|
def get_feature_value(self, key: str, fallback):
|
|
674
|
-
res = self.
|
|
674
|
+
res = self.eval_feature(key)
|
|
675
675
|
return res.value if res.value is not None else fallback
|
|
676
676
|
|
|
677
677
|
# @deprecated, use eval_feature
|
|
@@ -753,7 +753,7 @@ class GrowthBook(object):
|
|
|
753
753
|
self._subscriptions.add(callback)
|
|
754
754
|
return lambda: self._subscriptions.remove(callback)
|
|
755
755
|
|
|
756
|
-
def _track(self, experiment: Experiment, result: Result) -> None:
|
|
756
|
+
def _track(self, experiment: Experiment, result: Result, user_context: UserContext) -> None:
|
|
757
757
|
if not self._trackingCallback:
|
|
758
758
|
return None
|
|
759
759
|
key = (
|
|
@@ -764,7 +764,7 @@ class GrowthBook(object):
|
|
|
764
764
|
)
|
|
765
765
|
if not self._tracked.get(key):
|
|
766
766
|
try:
|
|
767
|
-
self._trackingCallback(experiment=experiment, result=result)
|
|
767
|
+
self._trackingCallback(experiment=experiment, result=result, user_context=user_context)
|
|
768
768
|
self._tracked[key] = True
|
|
769
769
|
except Exception:
|
|
770
770
|
pass
|
growthbook/growthbook_client.py
CHANGED
|
@@ -317,6 +317,10 @@ class GrowthBookClient:
|
|
|
317
317
|
}
|
|
318
318
|
self._sticky_bucket_cache_lock = False
|
|
319
319
|
|
|
320
|
+
# Plugin support
|
|
321
|
+
self._tracking_plugins: List[Any] = self.options.tracking_plugins or []
|
|
322
|
+
self._initialized_plugins: List[Any] = []
|
|
323
|
+
|
|
320
324
|
self._features_repository = (
|
|
321
325
|
EnhancedFeatureRepository(
|
|
322
326
|
self.options.api_host or "https://cdn.growthbook.io",
|
|
@@ -330,9 +334,12 @@ class GrowthBookClient:
|
|
|
330
334
|
|
|
331
335
|
self._global_context: Optional[GlobalContext] = None
|
|
332
336
|
self._context_lock = asyncio.Lock()
|
|
337
|
+
|
|
338
|
+
# Initialize plugins
|
|
339
|
+
self._initialize_plugins()
|
|
333
340
|
|
|
334
341
|
|
|
335
|
-
def _track(self, experiment: Experiment, result: Result) -> None:
|
|
342
|
+
def _track(self, experiment: Experiment, result: Result, user_context: UserContext) -> None:
|
|
336
343
|
"""Thread-safe tracking implementation"""
|
|
337
344
|
if not self.options.on_experiment_viewed:
|
|
338
345
|
return
|
|
@@ -348,7 +355,7 @@ class GrowthBookClient:
|
|
|
348
355
|
with self._tracked_lock:
|
|
349
356
|
if not self._tracked.get(key):
|
|
350
357
|
try:
|
|
351
|
-
self.options.on_experiment_viewed(experiment
|
|
358
|
+
self.options.on_experiment_viewed(experiment, result, user_context)
|
|
352
359
|
self._tracked[key] = True
|
|
353
360
|
except Exception:
|
|
354
361
|
logger.exception("Error in tracking callback")
|
|
@@ -492,25 +499,25 @@ class GrowthBookClient:
|
|
|
492
499
|
"""Evaluate a feature with proper async context management"""
|
|
493
500
|
async with self._context_lock:
|
|
494
501
|
context = await self.create_evaluation_context(user_context)
|
|
495
|
-
result = core_eval_feature(key=key, evalContext=context)
|
|
502
|
+
result = core_eval_feature(key=key, evalContext=context, tracking_cb=self._track)
|
|
496
503
|
return result
|
|
497
504
|
|
|
498
505
|
async def is_on(self, key: str, user_context: UserContext) -> bool:
|
|
499
506
|
"""Check if a feature is enabled with proper async context management"""
|
|
500
507
|
async with self._context_lock:
|
|
501
508
|
context = await self.create_evaluation_context(user_context)
|
|
502
|
-
return core_eval_feature(key=key, evalContext=context).on
|
|
509
|
+
return core_eval_feature(key=key, evalContext=context, tracking_cb=self._track).on
|
|
503
510
|
|
|
504
511
|
async def is_off(self, key: str, user_context: UserContext) -> bool:
|
|
505
512
|
"""Check if a feature is set to off with proper async context management"""
|
|
506
513
|
async with self._context_lock:
|
|
507
514
|
context = await self.create_evaluation_context(user_context)
|
|
508
|
-
return core_eval_feature(key=key, evalContext=context).off
|
|
515
|
+
return core_eval_feature(key=key, evalContext=context, tracking_cb=self._track).off
|
|
509
516
|
|
|
510
517
|
async def get_feature_value(self, key: str, fallback: Any, user_context: UserContext) -> Any:
|
|
511
518
|
async with self._context_lock:
|
|
512
519
|
context = await self.create_evaluation_context(user_context)
|
|
513
|
-
result = core_eval_feature(key=key, evalContext=context)
|
|
520
|
+
result = core_eval_feature(key=key, evalContext=context, tracking_cb=self._track)
|
|
514
521
|
return result.value if result.value is not None else fallback
|
|
515
522
|
|
|
516
523
|
async def run(self, experiment: Experiment, user_context: UserContext) -> Result:
|
|
@@ -539,4 +546,37 @@ class GrowthBookClient:
|
|
|
539
546
|
|
|
540
547
|
# Clear context
|
|
541
548
|
async with self._context_lock:
|
|
542
|
-
self._global_context = None
|
|
549
|
+
self._global_context = None
|
|
550
|
+
|
|
551
|
+
# Cleanup plugins
|
|
552
|
+
self._cleanup_plugins()
|
|
553
|
+
|
|
554
|
+
def _initialize_plugins(self) -> None:
|
|
555
|
+
"""Initialize all tracking plugins with this GrowthBookClient instance."""
|
|
556
|
+
for plugin in self._tracking_plugins:
|
|
557
|
+
try:
|
|
558
|
+
if hasattr(plugin, 'initialize'):
|
|
559
|
+
# Plugin is a class instance with initialize method
|
|
560
|
+
plugin.initialize(self)
|
|
561
|
+
self._initialized_plugins.append(plugin)
|
|
562
|
+
logger.debug(f"Initialized plugin: {plugin.__class__.__name__}")
|
|
563
|
+
elif callable(plugin):
|
|
564
|
+
# Plugin is a callable function
|
|
565
|
+
plugin(self)
|
|
566
|
+
self._initialized_plugins.append(plugin)
|
|
567
|
+
logger.debug(f"Initialized callable plugin: {plugin.__name__}")
|
|
568
|
+
else:
|
|
569
|
+
logger.warning(f"Plugin {plugin} is neither callable nor has initialize method")
|
|
570
|
+
except Exception as e:
|
|
571
|
+
logger.error(f"Failed to initialize plugin {plugin}: {e}")
|
|
572
|
+
|
|
573
|
+
def _cleanup_plugins(self) -> None:
|
|
574
|
+
"""Cleanup all initialized plugins."""
|
|
575
|
+
for plugin in self._initialized_plugins:
|
|
576
|
+
try:
|
|
577
|
+
if hasattr(plugin, 'cleanup'):
|
|
578
|
+
plugin.cleanup()
|
|
579
|
+
logger.debug(f"Cleaned up plugin: {plugin.__class__.__name__}")
|
|
580
|
+
except Exception as e:
|
|
581
|
+
logger.error(f"Error cleaning up plugin {plugin}: {e}")
|
|
582
|
+
self._initialized_plugins.clear()
|
|
@@ -2,14 +2,16 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
import threading
|
|
4
4
|
import time
|
|
5
|
-
from typing import Dict, Any, Optional, List, Callable
|
|
5
|
+
from typing import Dict, Any, Optional, List, Callable, TYPE_CHECKING
|
|
6
6
|
from .base import GrowthBookPlugin
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
import requests # type: ignore
|
|
10
|
+
else:
|
|
11
|
+
try:
|
|
12
|
+
import requests # type: ignore
|
|
13
|
+
except ImportError:
|
|
14
|
+
requests = None
|
|
13
15
|
|
|
14
16
|
logger = logging.getLogger("growthbook.plugins.growthbook_tracking")
|
|
15
17
|
|
|
@@ -88,22 +90,43 @@ class GrowthBookTrackingPlugin(GrowthBookPlugin):
|
|
|
88
90
|
super().cleanup()
|
|
89
91
|
|
|
90
92
|
def _setup_experiment_tracking(self, gb_instance) -> None:
|
|
91
|
-
"""Setup experiment tracking."""
|
|
92
|
-
original_callback = getattr(gb_instance, '_trackingCallback', None)
|
|
93
|
+
"""Setup experiment tracking for both legacy and async clients."""
|
|
93
94
|
|
|
94
|
-
def tracking_wrapper(experiment, result):
|
|
95
|
+
def tracking_wrapper(experiment, result, user_context=None):
|
|
95
96
|
# Track to ingestor
|
|
96
97
|
self._track_experiment_viewed(experiment, result)
|
|
97
98
|
|
|
98
99
|
# Call additional callback
|
|
99
100
|
if self.additional_callback:
|
|
100
|
-
self._safe_execute(self.additional_callback, experiment, result)
|
|
101
|
+
self._safe_execute(self.additional_callback, experiment, result, user_context)
|
|
102
|
+
|
|
103
|
+
# Check if it's the legacy GrowthBook client (has _trackingCallback)
|
|
104
|
+
if hasattr(gb_instance, '_trackingCallback'):
|
|
105
|
+
# Legacy GrowthBook client
|
|
106
|
+
original_callback = getattr(gb_instance, '_trackingCallback', None)
|
|
107
|
+
|
|
108
|
+
def legacy_wrapper(experiment, result, user_context=None):
|
|
109
|
+
tracking_wrapper(experiment, result, user_context)
|
|
110
|
+
# Call original callback
|
|
111
|
+
if original_callback:
|
|
112
|
+
self._safe_execute(original_callback, experiment, result, user_context)
|
|
113
|
+
|
|
114
|
+
gb_instance._trackingCallback = legacy_wrapper
|
|
115
|
+
|
|
116
|
+
elif hasattr(gb_instance, 'options') and hasattr(gb_instance.options, 'on_experiment_viewed'):
|
|
117
|
+
# New GrowthBookClient (async)
|
|
118
|
+
original_callback = gb_instance.options.on_experiment_viewed
|
|
119
|
+
|
|
120
|
+
def async_wrapper(experiment, result, user_context):
|
|
121
|
+
tracking_wrapper(experiment, result, user_context)
|
|
122
|
+
# Call original callback
|
|
123
|
+
if original_callback:
|
|
124
|
+
self._safe_execute(original_callback, experiment, result, user_context)
|
|
101
125
|
|
|
102
|
-
|
|
103
|
-
if original_callback:
|
|
104
|
-
self._safe_execute(original_callback, experiment, result)
|
|
126
|
+
gb_instance.options.on_experiment_viewed = async_wrapper
|
|
105
127
|
|
|
106
|
-
|
|
128
|
+
else:
|
|
129
|
+
self.logger.warning("_trackingCallback or on_experiment_viewed properties not found - tracking may not work properly")
|
|
107
130
|
|
|
108
131
|
def _setup_feature_tracking(self, gb_instance):
|
|
109
132
|
"""Setup feature evaluation tracking."""
|
|
@@ -77,6 +77,7 @@ class RequestContextPlugin(GrowthBookPlugin):
|
|
|
77
77
|
self.client_side_attributes = client_side_attributes
|
|
78
78
|
self.extract_utm = extract_utm
|
|
79
79
|
self.extract_user_agent = extract_user_agent
|
|
80
|
+
self._extracted_attributes: Dict[str, Any] = {}
|
|
80
81
|
|
|
81
82
|
def initialize(self, gb_instance) -> None:
|
|
82
83
|
"""Initialize plugin - extract attributes from request context."""
|
|
@@ -87,18 +88,33 @@ class RequestContextPlugin(GrowthBookPlugin):
|
|
|
87
88
|
request_attributes = self._extract_all_attributes()
|
|
88
89
|
|
|
89
90
|
if request_attributes:
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
# Check client type and merge attributes accordingly
|
|
92
|
+
if hasattr(gb_instance, 'get_attributes') and hasattr(gb_instance, 'set_attributes'):
|
|
93
|
+
# Legacy GrowthBook client
|
|
94
|
+
current_attributes = gb_instance.get_attributes()
|
|
95
|
+
merged_attributes = {**request_attributes, **current_attributes}
|
|
96
|
+
gb_instance.set_attributes(merged_attributes)
|
|
97
|
+
self.logger.info(f"Extracted {len(request_attributes)} request attributes for legacy client")
|
|
98
|
+
|
|
99
|
+
elif hasattr(gb_instance, 'options'):
|
|
100
|
+
# New GrowthBookClient - store attributes for future use
|
|
101
|
+
# Note: GrowthBookClient doesn't have get/set_attributes, but we can store
|
|
102
|
+
# the extracted attributes for potential future use or logging
|
|
103
|
+
self._extracted_attributes = request_attributes
|
|
104
|
+
self.logger.info(f"Extracted {len(request_attributes)} request attributes for async client (stored for reference)")
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
self.logger.warning("Unknown client type - cannot set attributes")
|
|
96
108
|
else:
|
|
97
109
|
self.logger.debug("No request context available")
|
|
98
110
|
|
|
99
111
|
except Exception as e:
|
|
100
112
|
self.logger.error(f"Failed to extract request attributes: {e}")
|
|
101
113
|
|
|
114
|
+
def get_extracted_attributes(self) -> Dict[str, Any]:
|
|
115
|
+
"""Get the attributes extracted from request context."""
|
|
116
|
+
return self._extracted_attributes.copy()
|
|
117
|
+
|
|
102
118
|
def _extract_all_attributes(self) -> Dict[str, Any]:
|
|
103
119
|
"""Extract all available attributes from request context."""
|
|
104
120
|
attributes = {}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
growthbook/__init__.py,sha256=u5eO2zI3G12ffZMXFXGG0baoDkdDG6mEzVbqU9Kq9-Q,444
|
|
2
|
+
growthbook/common_types.py,sha256=OUGkqoUuYetWz1cyA1eWz5DM3awYw_ExcNAjFqJuGAc,14881
|
|
3
|
+
growthbook/core.py,sha256=n9nwna26iZTY48LIvQqu5N_RrE35X0wlRBhq0-Qdb-s,35241
|
|
4
|
+
growthbook/growthbook.py,sha256=9YXfqyWYBj1Hh5GoPN-1Db3CApUhpRfG6at1yhiNQUw,30727
|
|
5
|
+
growthbook/growthbook_client.py,sha256=1bDIuJoxlKUR_bKe_gD6V7JlUPt53uGgix9DhgSkPPc,23360
|
|
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.0.dist-info/licenses/LICENSE,sha256=D-TcBckB0dTPUlNJ8jBiTIJIj1ekHLB1CY7HJtJKhMY,1069
|
|
12
|
+
growthbook-1.4.0.dist-info/METADATA,sha256=vwbaRVbHMEoqz1Elxrt_n3gKYCabg1spRJCtCviobLI,22073
|
|
13
|
+
growthbook-1.4.0.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
|
|
14
|
+
growthbook-1.4.0.dist-info/top_level.txt,sha256=dzfRQFGYejCIUstRSrrRVTMlxf7pBqASTI5S8gGRlXw,11
|
|
15
|
+
growthbook-1.4.0.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
growthbook/__init__.py,sha256=2pw6uyYKI4IQ59LZbCUsNroy4LLFZ_5XyPmscPUyAwI,444
|
|
2
|
-
growthbook/common_types.py,sha256=1jDWwS_9L0z_AZb5AJq1arEqi6QU66N3S4vYzkcDFDY,14728
|
|
3
|
-
growthbook/core.py,sha256=LCz5f5rchUL056kgJxgH566NoZWr2nr4hhL7MRlyNiM,35184
|
|
4
|
-
growthbook/growthbook.py,sha256=IJmFKTx87zAwQ6nO_Gepm5-S_Mtwf-lnt2RFBviB6Lk,30672
|
|
5
|
-
growthbook/growthbook_client.py,sha256=zLgoBSBCSfCb8OE0b-XQiDAEB4djTiMSrbNpqBui-M0,21454
|
|
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=aL_dgsPt51IZNxRVZVA5U3QEiGW-e-l5tC7Owl6CZO0,10032
|
|
10
|
-
growthbook/plugins/request_context.py,sha256=7_9LOfS5nXbxXE7KJ-zNT4Ug4-_698A4c3t0hjr6l4c,11895
|
|
11
|
-
growthbook-1.3.1.dist-info/licenses/LICENSE,sha256=D-TcBckB0dTPUlNJ8jBiTIJIj1ekHLB1CY7HJtJKhMY,1069
|
|
12
|
-
growthbook-1.3.1.dist-info/METADATA,sha256=FwTzARVgHtfSH2ZnCWVq0G9k5ISiPyQDIeYjabR3vCQ,22073
|
|
13
|
-
growthbook-1.3.1.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
|
|
14
|
-
growthbook-1.3.1.dist-info/top_level.txt,sha256=dzfRQFGYejCIUstRSrrRVTMlxf7pBqASTI5S8gGRlXw,11
|
|
15
|
-
growthbook-1.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|