
    -i.                    j   % S r SSKJr  SSKrSSKJrJrJrJr  SSK	J
r
  SSKJr  SSKJrJrJrJrJr  SS	KJrJr  S
S/r\(       a  SSKrSSKJrJrJr  SSKJr  O\rSS jr\" S5      r\" S5      r 0 r!S\"S'   SSSSS.           SS jjr#        SS jr$ " S S\5      r%      SS jr&g)z]
Public testing utilities.

See also _lib._testing for additional private testing utilities.
    )annotationsN)CallableIterableIteratorSequence)wraps)
ModuleType)TYPE_CHECKINGAny	ParamSpecTypeVarcast   )is_dask_namespaceis_jax_namespacelazy_xp_functionpatch_lazy_xp_functions)GraphKeySchedulerGetCallable)overridec                    U $ N )funcs    \/var/www/html/venv/lib/python3.13/site-packages/sklearn/externals/array_api_extra/testing.pyr   r      s        PTzdict[object, dict[str, Any]]_ufuncs_tagsTallow_dask_computejax_jitstatic_argnumsstatic_argnamesc               R    UUUUS.n XPl         g! [         a    U[        U '    gf = f)a  
Tag a function to be tested on lazy backends.

Tag a function so that when any tests are executed with ``xp=jax.numpy`` the
function is replaced with a jitted version of itself, and when it is executed with
``xp=dask.array`` the function will raise if it attempts to materialize the graph.
This will be later expanded to provide test coverage for other lazy backends.

In order for the tag to be effective, the test or a fixture must call
:func:`patch_lazy_xp_functions`.

Parameters
----------
func : callable
    Function to be tested.
allow_dask_compute : int, optional
    Number of times `func` is allowed to internally materialize the Dask graph. This
    is typically triggered by ``bool()``, ``float()``, or ``np.asarray()``.

    Set to 1 if you are aware that `func` converts the input parameters to NumPy and
    want to let it do so at least for the time being, knowing that it is going to be
    extremely detrimental for performance.

    If a test needs values higher than 1 to pass, it is a canary that the conversion
    to NumPy/bool/float is happening multiple times, which translates to multiple
    computations of the whole graph. Short of making the function fully lazy, you
    should at least add explicit calls to ``np.asarray()`` early in the function.
    *Note:* the counter of `allow_dask_compute` resets after each call to `func`, so
    a test function that invokes `func` multiple times should still work with this
    parameter set to 1.

    Default: 0, meaning that `func` must be fully lazy and never materialize the
    graph.
jax_jit : bool, optional
    Set to True to replace `func` with ``jax.jit(func)`` after calling the
    :func:`patch_lazy_xp_functions` test helper with ``xp=jax.numpy``. Set to False
    if `func` is only compatible with eager (non-jitted) JAX. Default: True.
static_argnums : int | Sequence[int], optional
    Passed to jax.jit. Positional arguments to treat as static (compile-time
    constant). Default: infer from `static_argnames` using
    `inspect.signature(func)`.
static_argnames : str | Iterable[str], optional
    Passed to jax.jit. Named arguments to treat as static (compile-time constant).
    Default: infer from `static_argnums` using `inspect.signature(func)`.

See Also
--------
patch_lazy_xp_functions : Companion function to call from the test or fixture.
jax.jit : JAX function to compile a function for performance.

Examples
--------
In ``test_mymodule.py``::

  from array_api_extra.testing import lazy_xp_function from mymodule import myfunc

  lazy_xp_function(myfunc)

  def test_myfunc(xp):
      a = xp.asarray([1, 2])
      # When xp=jax.numpy, this is the same as `b = jax.jit(myfunc)(a)`
      # When xp=dask.array, crash on compute() or persist()
      b = myfunc(a)

Notes
-----
In order for this tag to be effective, the test function must be imported into the
test module globals without its namespace; alternatively its namespace must be
declared in a ``lazy_xp_modules`` list in the test module globals.

Example 1::

  from mymodule import myfunc

  lazy_xp_function(myfunc)

  def test_myfunc(xp):
      x = myfunc(xp.asarray([1, 2]))

Example 2::

  import mymodule

  lazy_xp_modules = [mymodule]
  lazy_xp_function(mymodule.myfunc)

  def test_myfunc(xp):
      x = mymodule.myfunc(xp.asarray([1, 2]))

A test function can circumvent this monkey-patching system by using a namespace
outside of the two above patterns. You need to sanitize your code to make sure this
only happens intentionally.

Example 1::

  import mymodule
  from mymodule import myfunc

  lazy_xp_function(myfunc)

  def test_myfunc(xp):
      a = xp.asarray([1, 2])
      b = myfunc(a)  # This is wrapped when xp=jax.numpy or xp=dask.array
      c = mymodule.myfunc(a)  # This is not

Example 2::

  import mymodule

  class naked:
      myfunc = mymodule.myfunc

  lazy_xp_modules = [mymodule]
  lazy_xp_function(mymodule.myfunc)

  def test_myfunc(xp):
      a = xp.asarray([1, 2])
      b = mymodule.myfunc(a)  # This is wrapped when xp=jax.numpy or xp=dask.array
      c = naked.myfunc(a)  # This is not
r!   N)_lazy_xp_functionAttributeErrorr    )r   r"   r#   r$   r%   tagss         r   r   r   '   s<    B 1(*	D"!% "!T"s    &&c          
       ^ [        [        U R                  5      nU/[        [        [           [	        US/ 5      5      Qm  SU4S jjn[        U5      (       a4  U" 5        H(  u  p5pgUS   n[        Xh5      n	UR                  X5U	5        M*     g[        U5      (       ab  SSK	n
U" 5        HR  u  p5pgUS   (       d  M  [        [        S[        4   U
R                  UUS   US	   S
95      n	UR                  X5U	5        MT     gg)a`  
Test lazy execution of functions tagged with :func:`lazy_xp_function`.

If ``xp==jax.numpy``, search for all functions which have been tagged with
:func:`lazy_xp_function` in the globals of the module that defines the current test,
as well as in the ``lazy_xp_modules`` list in the globals of the same module,
and wrap them with :func:`jax.jit`. Unwrap them at the end of the test.

If ``xp==dask.array``, wrap the functions with a decorator that disables
``compute()`` and ``persist()`` and ensures that exceptions and warnings are raised
eagerly.

This function should be typically called by your library's `xp` fixture that runs
tests on multiple backends::

    @pytest.fixture(params=[numpy, array_api_strict, jax.numpy, dask.array])
    def xp(request, monkeypatch):
        patch_lazy_xp_functions(request, monkeypatch, xp=request.param)
        return request.param

but it can be otherwise be called by the test itself too.

Parameters
----------
request : pytest.FixtureRequest
    Pytest fixture, as acquired by the test itself or by one of its fixtures.
monkeypatch : pytest.MonkeyPatch
    Pytest fixture, as acquired by the test itself or by one of its fixtures.
xp : array_namespace
    Array namespace to be tested.

See Also
--------
lazy_xp_function : Tag a function to be tested on lazy backends.
pytest.FixtureRequest : `request` test function parameter.
lazy_xp_modulesc               3    >#    T H  n U R                   R                  5        Hu  u  pS n[        R                  " [        5         UR
                  nS S S 5        Uc1  [        R                  " [        [        5         [        U   nS S S 5        Uc  Mo  XX#4v   Mw     M     g ! , (       d  f       NV= f! , (       d  f       N3= f7fr   )	__dict__items
contextlibsuppressr(   r'   KeyError	TypeErrorr    )modnamer   r)   modss       r   iter_tagged,patch_lazy_xp_functions.<locals>.iter_tagged   s      C!ll002
.2((811D 9<#,,XyA+D1 B#T// 3  98 BAs<   ACB!*C;
B2	CC!
B/+C2
C <Cr"   r   Nr#   .r$   r%   )r$   r%   )returnzDIterator[tuple[ModuleType, str, Callable[..., Any], dict[str, Any]]])r   r	   modulelistgetattrr   
_dask_wrapsetattrr   jaxr   r   jit)requestmonkeypatchxpr3   r6   r4   r   r)   nwrappedr>   r5   s              @r   r   r      s   N z7>>
*CN$tJ'6G)LMND0L0 %0]!Ct)*A )G73 &3
 
"		%0]!CtIS#X&GG'+,<'=(,->(?   ##Cw7 &3 
r   c                  V    \ rS rSr% SrS\S'   S\S'   S\S'   SS jr\SS	 j5       rS
r	g)CountingDaskScheduleri  af  
Dask scheduler that counts how many times `dask.compute` is called.

If the number of times exceeds 'max_count', it raises an error.
This is a wrapper around Dask's own 'synchronous' scheduler.

Parameters
----------
max_count : int
    Maximum number of allowed calls to `dask.compute`.
msg : str
    Assertion to raise when the count exceeds `max_count`.
intcount	max_countstrmsgc                *    SU l         Xl        X l        g )Nr   rH   rI   rK   )selfrI   rK   s      r   __init__CountingDaskScheduler.__init__  s    
"r   c                    SS K nU =R                  S-  sl        U R                  U R                  ::  d   U R                  5       eUR                  " X40 UD6$ )Nr   r   )daskrH   rI   rK   get)rN   dskkeyskwargsrR   s        r   __call__CountingDaskScheduler.__call__  sG    

a
 zzT^^+5TXX5+xx,V,,r   rM   N)rI   rG   rK   rJ   )rT   r   rU   zSequence[Key] | KeyrV   r   r8   r   )
__name__
__module____qualname____firstlineno____doc____annotations__rO   r   rW   __static_attributes__r   r   r   rF   rF     s1     JN	H
 - -r   rF   c           	        ^ ^^^ SSK m[        T S[        T 5      5      nT(       a  ST 3OSnSTS-    SU S	U S
TS-    S3	m[        T 5      SUU UU4S jj5       nU$ )z
Wrap `func` to raise if it attempts to call `dask.compute` more than `n` times.

After the function returns, materialize the graph in order to re-raise exceptions.
r   NrY   zonly up to noz,Called `dask.compute()` or `dask.persist()` r   z times, but z* calls are allowed. Set `lazy_xp_function(z, allow_dask_compute=zA)` to allow for more (but note that this will harm performance). c                    > [        TT5      nTR                  R                  SU05         T" U 0 UD6nS S S 5        TR                  WSS9S   $ ! , (       d  f       N!= f)N	schedulerthreads)rc   r   )rF   configsetpersist)argsrV   rc   outrR   r   rK   rC   s       r   wrapper_dask_wrap.<locals>.wrapper9  s[    )!S1	[[__k956''C 7 ||C9|5a88 76s   	A
A)rh   zP.argsrV   zP.kwargsr8   r   )rR   r;   rJ   r   )r   rC   	func_namen_strrj   rR   rK   s   ``   @@r   r<   r<   &  s     j#d)4I!"k!E
6q1ug >g &K'<QUG DI	I  4[9 9 9 Nr   )r   objectr8   rn   )r   zCallable[..., Any]r"   rG   r#   boolr$   zint | Sequence[int] | Noner%   zstr | Iterable[str] | Noner8   None)r@   zpytest.FixtureRequestrA   zpytest.MonkeyPatchrB   r	   r8   rp   )r   Callable[P, T]rC   rG   r8   rq   )'r]   
__future__r   r/   collections.abcr   r   r   r   	functoolsr   typesr	   typingr
   r   r   r   r   _lib._utils._compatr   r   __all__pytestdask.typingr   r   r   typing_extensionsr   rn   r   r   r    r^   r   r   rF   r<   r   r   r   <module>r|      s   #  B B   ? ? D8
9<<* " cNCL-/* /  1526I"
I" I" 	I"
 /I" 0I" 
I"XL8"L81CL8LVL8	L8^!-0 !-H
 r   