7878from posthog .request import (
7979 AI_EVENTS_ENDPOINT ,
8080 EVENTS_ENDPOINT ,
81+ USER_AGENT as _USER_AGENT ,
8182 APIError ,
8283 QuotaLimitError ,
8384 RequestsConnectionError ,
8485 RequestsTimeout ,
86+ _get as _get_with_identity ,
87+ _remote_config as _remote_config_with_identity ,
8588 batch_post ,
8689 determine_server_host ,
8790 flags ,
@@ -374,6 +377,7 @@ def __init__(
374377 max_msg_size ,
375378 capture_mode ,
376379 capture_compression ,
380+ sdk_info ,
377381 eager_start ,
378382 ):
379383 self .name = name
@@ -391,6 +395,7 @@ def __init__(
391395 self .max_msg_size = max_msg_size
392396 self .capture_mode = capture_mode
393397 self .capture_compression = capture_compression
398+ self .sdk_info = sdk_info
394399 self ._max_queue_size = max_queue_size
395400 self ._thread_count = thread_count
396401 self ._eager_start = eager_start
@@ -426,6 +431,7 @@ def _start_locked(self) -> None:
426431 capture_mode = self .capture_mode ,
427432 capture_compression = self .capture_compression ,
428433 )
434+ consumer ._sdk_info = self .sdk_info
429435 consumer ._set_drain_signal (self ._drain_signal )
430436 self .consumers .append (consumer )
431437
@@ -913,6 +919,9 @@ def __init__(
913919 # `/i/v1/analytics/events`). Resolved here so the env-var fallback is
914920 # applied once; V0 is the default and keeps upgrades transparent.
915921 self .capture_mode = _resolve_capture_mode (capture_mode )
922+ self ._library_id = "posthog-python"
923+ self ._library_version = VERSION
924+ self ._sdk_info = f"{ self ._library_id } /{ self ._library_version } "
916925 # v1-only request compression; falls back to the legacy `gzip` flag when
917926 # neither the kwarg nor POSTHOG_CAPTURE_COMPRESSION is set.
918927 self .capture_compression = _resolve_capture_compression (
@@ -1033,6 +1042,7 @@ def __init__(
10331042 max_retries = self .max_retries ,
10341043 timeout = timeout ,
10351044 historical_migration = historical_migration ,
1045+ sdk_info = self ._sdk_info ,
10361046 )
10371047 self ._analytics_lane = _Lane (
10381048 name = "analytics" ,
@@ -1068,6 +1078,19 @@ def __init__(
10681078
10691079 self ._warn_if_duplicate_async_client ()
10701080
1081+ def _set_library_identity (self , library_id : str , library_version : str ) -> None :
1082+ """Override the SDK identity stamped on events and outbound requests."""
1083+ self ._library_id = library_id
1084+ self ._library_version = library_version
1085+ self ._sdk_info = f"{ library_id } /{ library_version } "
1086+ for lane in self ._lanes :
1087+ lane .sdk_info = self ._sdk_info
1088+ for consumer in lane .consumers :
1089+ consumer ._sdk_info = self ._sdk_info
1090+
1091+ def _request_identity_kwargs (self ) -> Dict [str , str ]:
1092+ return {"_user_agent" : self ._sdk_info } if self ._sdk_info != _USER_AGENT else {}
1093+
10711094 @property
10721095 def queue (self ) -> Queue :
10731096 """The analytics lane's queue (kept for backwards compatibility)."""
@@ -1481,6 +1504,8 @@ def _get_flags_decision(
14811504
14821505 if flag_keys_to_evaluate :
14831506 request_data ["flag_keys_to_evaluate" ] = flag_keys_to_evaluate
1507+ if self ._sdk_info != _USER_AGENT :
1508+ request_data ["_user_agent" ] = self ._sdk_info
14841509
14851510 resp_data = flags (
14861511 self .api_key ,
@@ -2279,8 +2304,8 @@ def _enqueue(self, msg, disable_geoip, lane=None, property_allowlist=None):
22792304
22802305 if not msg .get ("properties" ):
22812306 msg ["properties" ] = {}
2282- msg ["properties" ]["$lib" ] = "posthog-python"
2283- msg ["properties" ]["$lib_version" ] = VERSION
2307+ msg ["properties" ]["$lib" ] = self . _library_id
2308+ msg ["properties" ]["$lib_version" ] = self . _library_version
22842309
22852310 if disable_geoip is None :
22862311 disable_geoip = self .disable_geoip
@@ -2356,6 +2381,7 @@ def send_sync() -> None:
23562381 timeout = self .timeout ,
23572382 max_retries = self .max_retries ,
23582383 historical_migration = self .historical_migration ,
2384+ sdk_info = self ._sdk_info ,
23592385 )
23602386 return
23612387
@@ -2367,6 +2393,7 @@ def send_sync() -> None:
23672393 batch = [msg ],
23682394 historical_migration = self .historical_migration ,
23692395 path = lane .endpoint ,
2396+ ** self ._request_identity_kwargs (),
23702397 )
23712398
23722399 if lane .run_sync_if_open (send_sync ):
@@ -2925,12 +2952,14 @@ def _fetch_feature_flags_from_api(self):
29252952
29262953 cache_data_to_store : Optional [FlagDefinitionCacheData ] = None
29272954 try :
2928- response = get (
2955+ request_get = _get_with_identity if self ._request_identity_kwargs () else get
2956+ response = request_get (
29292957 personal_api_key ,
29302958 f"/flags/definitions?token={ self .api_key } &send_cohorts" ,
29312959 self .host ,
29322960 timeout = 10 ,
29332961 etag = request_etag ,
2962+ ** self ._request_identity_kwargs (),
29342963 )
29352964
29362965 with self ._flag_definition_publication_lock :
@@ -3865,12 +3894,18 @@ def get_remote_config_payload(self, key: str):
38653894 return None
38663895
38673896 try :
3868- return remote_config (
3897+ request_remote_config = (
3898+ _remote_config_with_identity
3899+ if self ._request_identity_kwargs ()
3900+ else remote_config
3901+ )
3902+ return request_remote_config (
38693903 self .personal_api_key ,
38703904 self .api_key ,
38713905 self .host ,
38723906 key ,
38733907 timeout = self .feature_flags_request_timeout_seconds ,
3908+ ** self ._request_identity_kwargs (),
38743909 )
38753910 except Exception as e :
38763911 self .log .exception (
0 commit comments