@@ -569,7 +569,7 @@ class GuildExperiment:
569569 An experiment that blocks the rollout of this experiment.
570570 aa_mode: :class:`bool`
571571 Whether the experiment is in A/A mode.
572- trigger_debugging:
572+ trigger_debugging: :class:`bool`
573573 Whether experiment analytics trigger debugging is enabled.
574574 """
575575
@@ -776,9 +776,12 @@ class UserExperiment:
776776 Whether the user has an explicit bucket override.
777777 population: :class:`int`
778778 The internal population group for the user, or None (-1) if manually overridden.
779+ holdout: Optional[:class:`UserExperiment`]
780+ An experiment that blocks the rollout of this experiment.
781+ Only present if the user is in the holdout.
779782 aa_mode: :class:`bool`
780783 Whether the experiment is in A/A mode.
781- trigger_debugging:
784+ trigger_debugging: :class:`bool`
782785 Whether experiment analytics trigger debugging is enabled.
783786 """
784787
@@ -793,10 +796,11 @@ class UserExperiment:
793796 '_result' ,
794797 'aa_mode' ,
795798 'trigger_debugging' ,
799+ 'holdout' ,
796800 )
797801
798802 def __init__ (self , * , state : ConnectionState , data : AssignmentPayload ):
799- (hash , revision , bucket , override , population , hash_result , aa_mode , trigger_debugging , * _ ) = data
803+ (hash , revision , bucket , override , population , hash_result , aa_mode , trigger_debugging , holdout_name , holdout_revision , holdout_bucket , * _ ) = data
800804
801805 self ._state = state
802806 self ._name : Optional [str ] = None
@@ -809,6 +813,29 @@ def __init__(self, *, state: ConnectionState, data: AssignmentPayload):
809813 self .aa_mode : bool = aa_mode == 1
810814 self .trigger_debugging : bool = trigger_debugging == 1
811815
816+ self .holdout : Optional [UserExperiment ] = None
817+ if holdout_name is not None :
818+ holdout_hash = murmurhash32 (holdout_name , signed = False )
819+ self .holdout = state .experiments .get (holdout_hash ) or UserExperiment .from_holdout (state , holdout_hash , holdout_name , holdout_revision , holdout_bucket )
820+
821+ @classmethod
822+ def from_holdout (cls , state : ConnectionState , hash : int , name : str , revision : Optional [int ], bucket : Optional [int ]) -> UserExperiment :
823+ self = cls .__new__ (cls )
824+ self ._state = state
825+ self ._name = name
826+ self .hash = hash
827+ self .revision = revision or 0
828+ self .assignment = bucket or - 1
829+
830+ # Most of these fields are defaults
831+ self .override = False
832+ self .population = 0
833+ self ._result = - 1
834+ self .aa_mode = False
835+ self .trigger_debugging = False
836+ self .holdout = None
837+ return self
838+
812839 def __repr__ (self ) -> str :
813840 return f'<UserExperiment hash={ self .hash } { f" name={ self ._name !r} " if self ._name else "" } bucket={ self .bucket } >'
814841
@@ -855,9 +882,11 @@ def result(self) -> int:
855882 :exc:`ValueError`
856883 The experiment name is unset without a precomputed result.
857884 """
858- if self ._result :
885+ if self ._result >= 0 :
859886 return self ._result
860887 elif not self .name :
861888 raise ValueError ('The experiment name must be set to compute the result' )
862889 else :
863- return murmurhash32 (f'{ self .name } :{ self ._state .self_id } ' , signed = False ) % 10000
890+ result = murmurhash32 (f'{ self .name } :{ self ._state .self_id } ' , signed = False ) % 10000
891+ self ._result = result
892+ return result
0 commit comments