growthbook 1.4.9__tar.gz → 1.4.10__tar.gz
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-1.4.9/growthbook.egg-info → growthbook-1.4.10}/PKG-INFO +1 -1
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/__init__.py +1 -1
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/growthbook.py +35 -6
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/growthbook_client.py +11 -2
- {growthbook-1.4.9 → growthbook-1.4.10/growthbook.egg-info}/PKG-INFO +1 -1
- {growthbook-1.4.9 → growthbook-1.4.10}/setup.cfg +1 -1
- {growthbook-1.4.9 → growthbook-1.4.10}/LICENSE +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/MANIFEST.in +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/README.md +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/common_types.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/core.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/plugins/__init__.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/plugins/base.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/plugins/growthbook_tracking.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/plugins/request_context.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook/py.typed +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook.egg-info/SOURCES.txt +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook.egg-info/dependency_links.txt +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook.egg-info/requires.txt +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/growthbook.egg-info/top_level.txt +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/pyproject.toml +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/setup.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/tests/conftest.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/tests/test_etag.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/tests/test_growthbook.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/tests/test_growthbook_client.py +0 -0
- {growthbook-1.4.9 → growthbook-1.4.10}/tests/test_plugins.py +0 -0
|
@@ -409,16 +409,35 @@ class FeatureRepository(object):
|
|
|
409
409
|
self._notify_feature_update_callbacks(res)
|
|
410
410
|
return res
|
|
411
411
|
return cached
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def user_agent_suffix(self) -> Optional[str]:
|
|
415
|
+
return getattr(self, "_user_agent_suffix", None)
|
|
416
|
+
|
|
417
|
+
@user_agent_suffix.setter
|
|
418
|
+
def user_agent_suffix(self, value: Optional[str]) -> None:
|
|
419
|
+
self._user_agent_suffix = value
|
|
412
420
|
|
|
413
421
|
# Perform the GET request (separate method for easy mocking)
|
|
414
422
|
def _get(self, url: str, headers: Optional[Dict[str, str]] = None):
|
|
415
423
|
self.http = self.http or PoolManager()
|
|
416
424
|
return self.http.request("GET", url, headers=headers or {})
|
|
425
|
+
|
|
426
|
+
def _get_headers(self, client_key: str, existing_headers: Dict[str, str] = None) -> Dict[str, str]:
|
|
427
|
+
headers = existing_headers or {}
|
|
428
|
+
headers['Accept-Encoding'] = "gzip, deflate"
|
|
429
|
+
|
|
430
|
+
# Add User-Agent with optional suffix
|
|
431
|
+
ua = "Gb-Python"
|
|
432
|
+
ua += f"-{self.user_agent_suffix}" if self.user_agent_suffix else f"-{client_key[-4:]}"
|
|
433
|
+
headers['User-Agent'] = ua
|
|
434
|
+
|
|
435
|
+
return headers
|
|
417
436
|
|
|
418
437
|
def _fetch_and_decode(self, api_host: str, client_key: str) -> Optional[Dict]:
|
|
419
438
|
url = self._get_features_url(api_host, client_key)
|
|
420
|
-
headers
|
|
421
|
-
|
|
439
|
+
headers = self._get_headers(client_key)
|
|
440
|
+
logger.debug(f"Fetching features from {url} with headers {headers}")
|
|
422
441
|
|
|
423
442
|
# Check if we have a cached ETag for this URL
|
|
424
443
|
cached_etag = None
|
|
@@ -473,13 +492,13 @@ class FeatureRepository(object):
|
|
|
473
492
|
|
|
474
493
|
return decoded # type: ignore[no-any-return]
|
|
475
494
|
except Exception as e:
|
|
476
|
-
logger.
|
|
495
|
+
logger.error(f"Failed to decode feature JSON from GrowthBook API: {e}")
|
|
477
496
|
return None
|
|
478
497
|
|
|
479
498
|
async def _fetch_and_decode_async(self, api_host: str, client_key: str) -> Optional[Dict]:
|
|
480
499
|
url = self._get_features_url(api_host, client_key)
|
|
481
|
-
headers
|
|
482
|
-
|
|
500
|
+
headers = self._get_headers(client_key=client_key)
|
|
501
|
+
logger.debug(f"[Async] Fetching features from {url} with headers {headers}")
|
|
483
502
|
|
|
484
503
|
# Check if we have a cached ETag for this URL
|
|
485
504
|
cached_etag = None
|
|
@@ -535,7 +554,7 @@ class FeatureRepository(object):
|
|
|
535
554
|
logger.warning(f"HTTP request failed: {e}")
|
|
536
555
|
return None
|
|
537
556
|
except Exception as e:
|
|
538
|
-
logger.
|
|
557
|
+
logger.error(f"Failed to decode feature JSON from GrowthBook API: {e}")
|
|
539
558
|
return None
|
|
540
559
|
|
|
541
560
|
def decrypt_response(self, data, decryption_key: str):
|
|
@@ -1134,6 +1153,16 @@ class GrowthBook(object):
|
|
|
1134
1153
|
except Exception as e:
|
|
1135
1154
|
logger.error(f"Failed to initialize plugin {plugin}: {e}")
|
|
1136
1155
|
|
|
1156
|
+
@property
|
|
1157
|
+
def user_agent_suffix(self) -> Optional[str]:
|
|
1158
|
+
"""Get the suffix appended to the User-Agent header"""
|
|
1159
|
+
return feature_repo.user_agent_suffix
|
|
1160
|
+
|
|
1161
|
+
@user_agent_suffix.setter
|
|
1162
|
+
def user_agent_suffix(self, value: Optional[str]) -> None:
|
|
1163
|
+
"""Set a suffix to be appended to the User-Agent header"""
|
|
1164
|
+
feature_repo.user_agent_suffix = value
|
|
1165
|
+
|
|
1137
1166
|
def _cleanup_plugins(self) -> None:
|
|
1138
1167
|
"""Cleanup all initialized plugins."""
|
|
1139
1168
|
for plugin in self._initialized_plugins:
|
|
@@ -9,7 +9,7 @@ import asyncio
|
|
|
9
9
|
import threading
|
|
10
10
|
import traceback
|
|
11
11
|
from datetime import datetime
|
|
12
|
-
from growthbook import FeatureRepository
|
|
12
|
+
from growthbook import FeatureRepository, feature_repo
|
|
13
13
|
from contextlib import asynccontextmanager
|
|
14
14
|
|
|
15
15
|
from .core import eval_feature as core_eval_feature, run_experiment
|
|
@@ -569,7 +569,6 @@ class GrowthBookClient:
|
|
|
569
569
|
self._tracked.clear()
|
|
570
570
|
with self._subscriptions_lock:
|
|
571
571
|
self._subscriptions.clear()
|
|
572
|
-
|
|
573
572
|
# Clear context
|
|
574
573
|
async with self._context_lock:
|
|
575
574
|
self._global_context = None
|
|
@@ -577,6 +576,16 @@ class GrowthBookClient:
|
|
|
577
576
|
# Cleanup plugins
|
|
578
577
|
self._cleanup_plugins()
|
|
579
578
|
|
|
579
|
+
@property
|
|
580
|
+
def user_agent_suffix(self) -> Optional[str]:
|
|
581
|
+
"""Get the suffix appended to the User-Agent header"""
|
|
582
|
+
return feature_repo.user_agent_suffix
|
|
583
|
+
|
|
584
|
+
@user_agent_suffix.setter
|
|
585
|
+
def user_agent_suffix(self, value: Optional[str]) -> None:
|
|
586
|
+
"""Set a suffix to be appended to the User-Agent header"""
|
|
587
|
+
feature_repo.user_agent_suffix = value
|
|
588
|
+
|
|
580
589
|
def _initialize_plugins(self) -> None:
|
|
581
590
|
"""Initialize all tracking plugins with this GrowthBookClient instance."""
|
|
582
591
|
for plugin in self._tracking_plugins:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|