
    \jn                         d dl Z d dlmZmZmZmZmZmZ d dlm	Z	 d dl
mZ d dlmZ d dlmZ d ZdZd Z G d	 d
      Z G d d      Z G d d      Zy)    N)AnyDictListOptionalSetUnion)Context)FEATURES)_TestDataSource)ReadWriteLock   c                     | rt         S t        S N)TRUE_VARIATION_INDEXFALSE_VARIATION_INDEX)	variations    I/root/env/lib/python3.12/site-packages/ldclient/integrations/test_data.py_variation_for_booleanr      s    ##$$    c                   \    e Zd ZdZdZd Zd Zedd       Zde	ddfd	Z
dd
ZdefdZd Zy)TestDataa  A mechanism for providing dynamically updatable feature flag state in a
    simplified form to an SDK client in test scenarios.

    Unlike ``Files``, this mechanism does not use any external resources. It provides only
    the data that the application has put into it using the ``update`` method.
    ::

        td = TestData.data_source()
        td.update(td.flag('flag-key-1').variation_for_all_users(True))

        client = LDClient(config=Config('SDK_KEY', update_processor_class = td))

        # flags can be updated at any time:
        td.update(td.flag('flag-key-1'). \
            variation_for_user('some-user-key', True). \
            fallthrough_variation(False))

    The above example uses a simple boolean flag, but more complex configurations are possible using
    the methods of the ``FlagBuilder`` that is returned by ``flag``. ``FlagBuilder``
    supports many of the ways a flag can be configured on the LaunchDarkly dashboard, but does not
    currently support 1. rule operators other than "in" and "not in", or 2. percentage rollouts.

    If the same ``TestData`` instance is used to configure multiple ``LDClient`` instances,
    any changes made to the data will propagate to all of the ``LDClient`` instances.
    Fc                 L    i | _         i | _        t               | _        g | _        y r   )_flag_builders_current_flagsr   _lock
_instancesselfs    r   __init__zTestData.__init__0   s"      "_
r   c                     t        || |      }	 | j                  j                          | j                  j	                  |       | j                  j                          |S # | j                  j                          w xY wr   )r   r   lockr   appendunlock)r   configstorereadydata_sources        r   __call__zTestData.__call__6   s_    %eT59	 JJOOOO"";/JJ JJs   5A   A<returnc                      t               S )znCreates a new instance of the test data source.

        :return: a new configurable test data source
        )r    r   r   r'   zTestData.data_source@   s     zr   keyFlagBuilderc                    	 | j                   j                          || j                  v rF| j                  |   r7| j                  |   j                         | j                   j	                          S t        |      j                         | j                   j	                          S # | j                   j	                          w xY w)a/  Creates or copies a ``FlagBuilder`` for building a test flag configuration.

        If this flag key has already been defined in this ``TestData`` instance, then the builder
        starts with the same configuration that was last provided for this flag.

        Otherwise, it starts with a new default configuration in which the flag has ``True`` and
        ``False`` variations, is ``True`` for all users when targeting is turned on and
        ``False`` otherwise, and currently has targeting turned on. You can change any of those
        properties, and provide more complex behavior, using the ``FlagBuilder`` methods.

        Once you have set the desired configuration, pass the builder to ``update``.

        :param str key: the flag key
        :return: the flag configuration builder object
        )r   rlockr   _copyrunlockr-   boolean_flagr   r,   s     r   flagzTestData.flagH   s     	!JJd)))d.A.A#.F**3/557 JJ  #3'446JJ DJJ s   AB# 0B# #B?c                    	 | j                   j                          d}|j                  | j                  v r | j                  |j                     }|r|d   }|j	                  |dz         }|| j                  |j                  <   |j                         | j                  |j                  <   | j                   j                          | j                  D ]  }|j                  |        | S # | j                   j                          w xY w)a  Updates the test data with the specified flag configuration.

        This has the same effect as if a flag were added or modified on the LaunchDarkly dashboard.
        It immediately propagates the flag change to any ``LDClient`` instance(s) that you have
        already configured to use this ``TestData``. If no ``LDClient`` has been started yet,
        it simply adds this flag to the test data which will be provided to any ``LDClient`` that
        you subsequently configure.

        Any subsequent changes to this ``FlagBuilder`` instance do not affect the test data,
        unless you call ``update`` again.

        :param flag_builder: a flag configuration builder
        :return: self (the TestData object)
        r   versionr   )
r   r!   _keyr   _buildr0   r   r#   r   upsert)r   flag_builderold_versionold_flagnew_flaginstances         r   updatezTestData.updatea   s    	 JJOOK  D$7$77..|/@/@A"*9"5K#**;?;H5=D 1 125A5G5G5ID 1 12JJ 	&HOOH%	&  JJs   B(C( (Dc                 L    t         t        j                  | j                        iS r   )r
   copyr   r   s    r   _make_init_datazTestData._make_init_data   s    499T%8%89;;r   c                     	 | j                   j                          | j                  j                  |       | j                   j	                          y # | j                   j	                          w xY wr   )r   r!   r   remover#   )r   r>   s     r   _closed_instancezTestData._closed_instance   sH    	 JJOOOO""8,JJDJJs   5A A.N)r)   r   )r:   r-   r)   r   )__name__
__module____qualname____doc____test__r   r(   staticmethodr'   strr4   r?   dictrB   rE   r+   r   r   r   r      sV    6 H  ! ! !2"H< < r   r   c                      e Zd ZdZdefdZd$dZdedd fdZde	ee
f   dd fd	Zde	ee
f   dd fd
Zd$dZd Zd$dZde	ee
f   dd fdZde	ee
f   dd fdZdedd fdZdedd fdZdede	ee
f   dd fdZdedede	ee
f   dd fdZd%dZdeddfdZdededdfdZdeddfdZdededdfdZd$dZd$d Zd!e
defd"Zy#)&r-   zA builder for feature flag configurations to be used with :class:`ldclient.integrations.test_data.TestData`.

    :see: :meth:`ldclient.integrations.test_data.TestData.flag()`
    :see: :meth:`ldclient.integrations.test_data.TestData.update()`
    r,   c                 f    || _         d| _        g | _        d| _        d| _        i | _        g | _        y)z-:param str key: The name of the flag
        TN)r7   _on_variations_off_variation_fallthrough_variation_targets_rulesr3   s     r   r   zFlagBuilder.__init__   s8     	"&*#r   r)   c                    t        | j                        }| j                  |_        t        j                  | j                        |_        | j
                  |_        | j                  |_        t               |_        | j                  j                         D ]'  \  }}t        j                  |      |j                  |<   ) t        j                  | j                        |_
        |S )zCreates a deep copy of the flag builder. Subsequent updates to the
        original ``FlagBuilder`` object will not update the copy and vise versa.

        :return: a copy of the flag builder object
        )r-   r7   rP   rA   rQ   rR   rS   rM   rT   itemsrU   )r   tokvs       r   r0   zFlagBuilder._copy   s     #4#3#34 //$($?$?!fMM'') 	*DAq!YYq\BKKN	*IIdkk*		r   onc                     || _         | S )a  Sets targeting to be on or off for this flag.

        The effect of this depends on the rest of the flag configuration, just as it does on the
        real LaunchDarkly dashboard. In the default configuration that you get from calling
        :meth:`ldclient.integrations.test_data.TestData.flag()` with a new flag key,
        the flag will return ``False`` whenever targeting is off, and ``True`` when
        targeting is on.

        :param on: ``True`` if targeting should be on
        :return: the flag builder
        )rP   )r   r[   s     r   r[   zFlagBuilder.on   s     r   r   c                 t    t        |t              r t        |      | j                         _        | S || _        | S )a!  Specifies the fallthrough variation. The fallthrough is the value
        that is returned if targeting is on and the user was not matched by a more specific
        target or rule.

        If the flag was previously configured with other variations and the variation
        specified is a boolean, this also changes it to a boolean flag.

        :param bool|int variation: ``True`` or ``False`` or the desired fallthrough variation index:
            ``0`` for the first, ``1`` for the second, etc.
        :return: the flag builder
        )
isinstanceboolr   r2   rS   r   r   s     r   fallthrough_variationz!FlagBuilder.fallthrough_variation   s7     i&9OPY9ZD6K*3D'Kr   c                 t    t        |t              r t        |      | j                         _        | S || _        | S )a  Specifies the fallthrough variation. This is the variation that is returned
        whenever targeting is off.

        If the flag was previously configured with other variations and the variation
        specified is a boolean, this also changes it to a boolean flag.

        :param bool|int variation: ``True`` or ``False`` or the desired off variation index:
            ``0`` for the first, ``1`` for the second, etc.
        :return: the flag builder
        )r^   r_   r   r2   rR   r`   s     r   off_variationzFlagBuilder.off_variation   s6     i&1G	1RD.K"+DKr   c                     | j                         r| S | j                  dd      j                  t              j	                  t
              S )a  A shortcut for setting the flag to use the standard boolean configuration.

        This is the default for all new flags created with
        :meth:`ldclient.integrations.test_data.TestData.flag()`.

        The flag will have two variations, ``True`` and ``False`` (in that order);
        it will return ``False`` whenever targeting is off, and ``True`` when targeting is on
        if no other settings specify otherwise.

        :return: the flag builder
        TF)_is_boolean_flag
variationsra   r   rc   r   r   s    r   r2   zFlagBuilder.boolean_flag   s?       "KOOD%0&&';<457r   c                     t        | j                        dk(  xr. | j                  t           dk(  xr | j                  t           dk(  S )N   TF)lenrQ   r   r   r   s    r   re   zFlagBuilder._is_boolean_flag   sO    D$$%* A  !56$>A  !675@	Br   c                 &    t        |      | _        | S )a,  Changes the allowable variation values for the flag.

        The value may be of any valid JSON type. For instance, a boolean flag
        normally has ``True, False``; a string-valued flag might have
        ``'red', 'green'``; etc.

        **Example:** A single variation
        ::

             td.flag('new-flag').variations(True)

        **Example:** Multiple variations
        ::

            td.flag('new-flag').variations('red', 'green', 'blue')

        :param variations: the the desired variations
        :return: the flag builder
        )listrQ   )r   rf   s     r   rf   zFlagBuilder.variations  s    (  
+r   c                 $    | j                  |      S )zDeprecated name for variation_for_all().

        .. deprecated:: 8.0.0
           Use :meth:`ldclient.integrations.test_data.FlagBuilder.variation_for_all()`.
        )variation_for_allr`   s     r   variation_for_all_usersz#FlagBuilder.variation_for_all_users  s     %%i00r   c                     t        |t              r(| j                         j                  t	        |            S | j                         j                         j                  d      j                  |      S )a  Sets the flag to always return the specified variation for all contexts.

        The variation is specified, targeting is switched on, and any existing targets or rules are removed.
        The fallthrough variation is set to the specified value. The off variation is left unchanged.

        If the flag was previously configured with other variations and the variation specified is a boolean,
        this also changes it to a boolean flag.

        :param bool|int variation: ``True`` or ``False`` or the desired variation index to return:
            ``0`` for the first, ``1`` for the second, etc.
        :return: the flag builder
        T)	r^   r_   r2   rn   r   clear_rulesclear_targetsr[   ra   r`   s     r   rm   zFlagBuilder.variation_for_all!  s^     i&$$&>>?UV_?`aa##%33588>TTU^__r   valuec                 $    | j                  |      S )zDeprecated name for value_for_all().

        .. deprecated:: 8.0.0
           Use :meth:`ldclient.integrations.test_data.FlagBuilder.value_for_all()`.
        )value_for_allr   rr   s     r   value_for_all_userszFlagBuilder.value_for_all_users3  s     !!%((r   c                 B    | j                  |      j                  d      S )a  
        Sets the flag to always return the specified variation value for all users.

        The value may be of any JSON type. This method changes the flag to have only
        a single variation, which is this value, and to return the same variation
        regardless of whether targeting is on or off. Any existing targets or rules
        are removed.

        :param value the desired value to be returned for all users
        :return the flag builder
        r   )rf   rn   ru   s     r   rt   zFlagBuilder.value_for_all;  s     u%==a@@r   user_keyc                 D    | j                  t        j                  ||      S )a:  Sets the flag to return the specified variation for a specific user key when targeting
        is on.

        This has no effect when targeting is turned off for the flag.

        If the flag was previously configured with other variations and the variation specified is a boolean,
        this also changes it to a boolean flag.

        :param user_key: a user key
        :param bool|int variation: ``True`` or ``False`` or the desired variation index to return:
            ``0`` for the first, ``1`` for the second, etc.
        :return: the flag builder
        )variation_for_keyr	   DEFAULT_KIND)r   rx   r   s      r   variation_for_userzFlagBuilder.variation_for_userI  s     %%g&:&:HiPPr   context_kindcontext_keyc                    t        |t              r*| j                         j                  ||t	        |            S | j
                  j                  |      }|i }|| j
                  |<   t        | j                        D ]W  \  }}||k(  r4|j                  |      }|t               }|||<   |j                  |       ?||v sD||   j                  |       Y | S )a  Sets the flag to return the specified variation for a specific context, identified
        by context kind and key, when targeting is on.

        This has no effect when targeting is turned off for the flag.

        If the flag was previously configured with other variations and the variation specified is a boolean,
        this also changes it to a boolean flag.

        :param context_kind: the context kind
        :param context_key: the context key
        :param bool|int variation: ``True`` or ``False`` or the desired variation index to return:
            ``0`` for the first, ``1`` for the second, etc.
        :return: the flag builder
        )r^   r_   r2   rz   r   rT   get	enumeraterQ   setadddiscard)r   r}   r~   r   targetsidxvartarget_for_variations           r   rz   zFlagBuilder.variation_for_keyY  s     i&$$&88{TjktTuvv --##L1?G*1DMM,'!$"2"23 	6HCy '.{{3'7$'/+.5(#7GCL %((5 '>CL((5	6  r   FlagRuleBuilderc                 :    | j                   j                  |       y r   )rU   r"   )r   flag_rule_builders     r   	_add_rulezFlagBuilder._add_rule  s    ,-r   	attributec                 F     | j                   t        j                  |g| S )aN  Starts defining a flag rule, using the "is one of" operator.

        This is a shortcut for calling :meth:`ldclient.integrations.test_data.FlagBuilder.if_match_context()`
        with "user" as the context kind.

        **Example:** create a rule that returns ``True`` if the name is "Patsy" or "Edina"
        ::

            td.flag("flag") \
                .if_match('name', 'Patsy', 'Edina') \
                .then_return(True)

        :param attribute: the user attribute to match against
        :param values: values to compare to
        :return: the flag rule builder
        )if_match_contextr	   r{   r   r   valuess      r   if_matchzFlagBuilder.if_match  s$    " %t$$W%9%99NvNNr   c                 @    t        |       } |j                  ||g| S )av  Starts defining a flag rule, using the "is one of" operator. This matching expression only
        applies to contexts of a specific kind.

        **Example:** create a rule that returns ``True`` if the name attribute for the
        company" context is "Ella" or "Monsoon":
        ::

            td.flag("flag") \
                .if_match_context('company', 'name', 'Ella', 'Monsoon') \
                .then_return(True)

        :param context_kind: the context kind
        :param attribute: the context attribute to match against
        :param values: values to compare to
        :return: the flag rule builder
        )r   and_match_contextr   r}   r   r   r   s        r   r   zFlagBuilder.if_match_context  s*    " ,D12 22<TVTTr   c                 F     | j                   t        j                  |g| S )ai  Starts defining a flag rule, using the "is not one of" operator.

        This is a shortcut for calling :meth:`ldclient.integrations.test_data.FlagBuilder.if_not_match_context()`
        with "user" as the context kind.

        **Example:** create a rule that returns ``True`` if the name is neither "Saffron" nor "Bubble"
        ::

            td.flag("flag") \
                .if_not_match('name', 'Saffron', 'Bubble') \
                .then_return(True)

        :param attribute: the user attribute to match against
        :param values: values to compare to
        :return: the flag rule builder
        )if_not_match_contextr	   r{   r   s      r   if_not_matchzFlagBuilder.if_not_match  s$    " )t(()=)=yR6RRr   c                 @    t        |       } |j                  ||g| S )a  Starts defining a flag rule, using the "is not one of" operator. This matching expression only
        applies to contexts of a specific kind.

        **Example:** create a rule that returns ``True`` if the name attribute for the
        "company" context is neither "Pendant" nor "Sterling Cooper":
        ::

            td.flag("flag") \
                .if_not_match('company', 'name', 'Pendant', 'Sterling Cooper') \
                .then_return(True)

        :param context_kind: the context kind
        :param attribute: the context attribute to match against
        :param values: values to compare to
        :return: the flag rule builder
        )r   and_not_match_contextr   s        r   r   z FlagBuilder.if_not_match_context  s+    " ,D16 66|YXQWXXr   c                     g | _         | S )zRemoves any existing rules from the flag.
        This undoes the effect of methods like
        :meth:`ldclient.integrations.test_data.FlagBuilder.if_match()`.

        :return: the same flag builder
        )rU   r   s    r   rp   zFlagBuilder.clear_rules  s     r   c                     i | _         | S )zRemoves any existing targets from the flag.
        This undoes the effect of methods like
        :meth:`ldclient.integrations.test_data.FlagBuilder.variation_for_user()`.

        :return: the same flag builder
        )rT   r   s    r   rq   zFlagBuilder.clear_targets  s     r   r6   c                    | j                   || j                  | j                  g dd}| j                  |d<   d| j                  i|d<   g }g }| j
                  j                         D ]  \  }}|j                         D ]{  \  }}|t        j                  k(  r<|j                  |t        t        |            d       |j                  ||g d       U|j                  ||t        t        |            d       }  ||d<   ||d	<   g }	t        | j                        D ].  \  }
}|	j                  |j                  t        |
                   0 |	|d
<   |S )zCreates a dictionary representation of the flag

        :param version: the version number of the rule
        :return: the dictionary representation of the flag
         )r,   r6   r[   rf   prerequisitessaltoffVariationr   fallthrough)r   r   )contextKindr   r   r   contextTargetsrules)r7   rP   rQ   rR   rS   rT   rW   r	   r{   r"   sortedrk   r   rU   r8   rL   )r   r6   base_flag_objectr   context_targetstarget_context_kindtarget_variations	var_indextarget_keysr   r   rules               r   r8   zFlagBuilder._build  s|    99((**
 ,0+>+>(T88+' 6:mm6I6I6K 	2!2*;*A*A*C &	;&'*>*>>NN%."(k):";$  $**':%."$,  $**':%."(k):";, 	$ '.#-<)*"4;;/ 	0ICLLSX./	0$)!r   N)r)   r-   )r   r   ) rF   rG   rH   rI   rL   r   r0   r_   r[   r   intra   rc   r2   re   rf   rn   rm   r   rv   rt   r|   rz   r   r   r   r   r   rp   rq   rM   r8   r+   r   r   r-   r-      s   
	C 	&T m uT3Y/? M &uT3Y'7 M $7&B
01tSy1A 1m 1`5s+; ` `$) ) )A3 A= AQ3 Q5s;K QP] Q )c ) )PUVZ\_V_P` )er )V.O# O3D O&US US UN_ U(Sc S7H S&Y Y YRc Y(0 c 0 d 0 r   r-   c                       e Zd ZdZdefdZdedd fdZdededd fdZdedd fd	Z	dededd fd
Z
deeef   ddfdZdedefdZy)r   a  
    A builder for feature flag rules to be used with :class:`ldclient.integrations.test_data.FlagBuilder`.

    In the LaunchDarkly model, a flag can have any number of rules, and a rule can have any number of
    clauses. A clause is an individual test such as "name is 'X'". A rule matches a user if all of the
    rule's clauses match the user.

    To start defining a rule, use one of the flag builder's matching methods such as
    :meth:`ldclient.integrations.test_data.FlagBuilder.if_match()`.
    This defines the first clause for the rule.  Optionally, you may add more
    clauses with the rule builder's methods such as
    :meth:`ldclient.integrations.test_data.FlagRuleBuilder.and_match()` or
    :meth:`ldclient.integrations.test_data.FlagRuleBuilder.and_not_match()`.
    Finally, call :meth:`ldclient.integrations.test_data.FlagRuleBuilder.then_return()`
    to finish defining the rule.
    r:   c                 .    || _         g | _        d | _        y r   )_flag_builder_clauses
_variation)r   r:   s     r   r   zFlagRuleBuilder.__init__/  s    )r   r   r)   c                 F     | j                   t        j                  |g| S )a}  Adds another clause, using the "is one of" operator.

        This is a shortcut for calling :meth:`ldclient.integrations.test_data.FlagRuleBuilder.and_match_context()`
        with "user" as the context kind.

        **Example:** create a rule that returns ``True`` if the name is "Patsy" and the country is "gb"
        ::

            td.flag('flag') \
                .if_match('name', 'Patsy') \
                .and_match('country', 'gb') \
                .then_return(True)

        :param attribute: the user attribute to match against
        :param values: values to compare to
        :return: the flag rule builder
        )r   r	   r{   r   s      r   	and_matchzFlagRuleBuilder.and_match4  s$    $ &t%%g&:&:IOOOr   r}   c                 Z    | j                   j                  ||dt        |      dd       | S )a  Adds another clause, using the "is one of" operator. This matching expression only
        applies to contexts of a specific kind.

        **Example:** create a rule that returns ``True`` if the name attribute for the
        "company" context is "Ella", and the country attribute for the "company" context is "gb":
        ::

            td.flag('flag') \
                .if_match_context('company', 'name', 'Ella') \
                .and_match_context('company', 'country', 'gb') \
                .then_return(True)

        :param context_kind: the context kind
        :param attribute: the context attribute to match against
        :param values: values to compare to
        :return: the flag rule builder
        inFr   r   opr   negater   r"   rk   r   r}   r   r   s       r   r   z!FlagRuleBuilder.and_match_contextH  s5    $ 	+&v, 	 r   c                 F     | j                   t        j                  |g| S )a  Adds another clause, using the "is not one of" operator.

        This is a shortcut for calling :meth:`ldclient.integrations.test_data.FlagRuleBuilder.and_not_match_context()`
        with "user" as the context kind.

        **Example:** create a rule that returns ``True`` if the name is "Patsy" and the country is not "gb"
        ::

            td.flag('flag') \
                .if_match('name', 'Patsy') \
                .and_not_match('country', 'gb') \
                .then_return(True)

        :param attribute: the user attribute to match against
        :param values: values to compare to
        :return: the flag rule builder
        )r   r	   r{   r   s      r   and_not_matchzFlagRuleBuilder.and_not_matchc  s$    $ *t))'*>*>	SFSSr   c                 Z    | j                   j                  ||dt        |      dd       | S )a  Adds another clause, using the "is not one of" operator. This matching expression only
        applies to contexts of a specific kind.

        **Example:** create a rule that returns ``True`` if the name attribute for the
        "company" context is "Ella", and the country attribute for the "company" context is not "gb":
        ::

            td.flag('flag') \
                .if_match_context('company', 'name', 'Ella') \
                .and_not_match_context('company', 'country', 'gb') \
                .then_return(True)

        :param context_kind: the context kind
        :param attribute: the context attribute to match against
        :param values: values to compare to
        :return: the flag rule builder
        r   Tr   r   r   s       r   r   z%FlagRuleBuilder.and_not_match_contextw  s5    $ 	+&v, 	 r   r   r-   c                     t        |t              r4| j                  j                          | j	                  t        |            S || _        | j                  j                  |        | j                  S )a  Finishes defining the rule, specifying the result as either a boolean
        or a variation index.

        If the flag was previously configured with other variations and the variation specified is a boolean,
        this also changes it to a boolean flag.

        :param bool|int variation: ``True`` or ``False`` or the desired  variation index:
            ``0`` for the first, ``1`` for the second, etc.
        :return:  the flag builder with this rule added
        )r^   r_   r   r2   then_returnr   r   r   r`   s     r   r   zFlagRuleBuilder.then_return  s^     i&++-##$:9$EFF'DO((.%%%r   idc                 <    d|z   | j                   | j                  dS )zCreates a dictionary representation of the rule

        :param id: the rule id
        :return: the dictionary representation of the rule
        r   )r   r   clauses)r   r   )r   r   s     r   r8   zFlagRuleBuilder._build  s#     2+}}
 	
r   N)rF   rG   rH   rI   r-   r   rL   r   r   r   r   r   r_   r   r   rM   r8   r+   r   r   r   r     s     [ 
P3 P4E P(c c O` 6Ts T8I T(# # Sd 6&U49%5 &- &*

 

 

r   r   )rA   typingr   r   r   r   r   r   ldclient.contextr	   ldclient.versioned_data_kindr
   5ldclient.impl.integrations.test_data.test_data_sourcer   ldclient.impl.rwlockr   r   r   r   r   r-   r   r+   r   r   <module>r      sQ     8 8 $ 1 Q .  %{  { zL  L ^S
 S
r   