o
    ;ήc{1                     @   s  d dl Z d dlZd dlmZ d dlmZmZmZmZm	Z	m
Z
mZmZmZ ejdkr0d dlmZ nd dlmZ d dlZd dlZd dlmZ d dlmZ d*deedf d	efd
dZedZedZedZG dd dZe ZG dd deZ G dd de
e Z!G dd deZd+ddZ"		d,deeeeeef f dedee	eef  d	eeef fddZ#	d-deeeeeef f d e$d	eeef fd!d"Z%d#dd$d%eeef d&e$d'eej& d	eeee f fd(d)Z'dS ).    N)import_module)	Any	AwaitableCallable	CoroutineDictGenericOptionalTypeVarUnion)   
   )	ParamSpec)threadlocals)	TaskGroupasynclib_namereturnc                 C   s@   | d u rt  } d|  }ztj| W S  ty   t| Y S w )Nzanyio._backends._)sniffiocurrent_async_librarysysmodulesKeyErrorr   )r   
modulename r   4/tmp/pip-target-vg8gfxp4/lib/python/asyncer/_main.pyget_asynclib   s   r   T_RetvalT_ParamSpecTc                   @   s   e Zd ZdefddZdS )PendingTyper   c                 C   s   dS )NAsyncerPendingr   selfr   r   r   __repr__/   s   zPendingType.__repr__N)__name__
__module____qualname__strr#   r   r   r   r   r   .   s    r   c                   @      e Zd ZdS )PendingValueExceptionNr$   r%   r&   r   r   r   r   r)   6       r)   c                   @   s:   e Zd Zd	ddZedefddZedefddZdS )
	SoonValuer   Nc                 C   s
   t | _d S N)Pending_stored_valuer!   r   r   r   __init__;   s   
zSoonValue.__init__c                 C   s   t | jtr
td| jS )Na  The return value of this task is still pending. Maybe you forgot to access it after the async with asyncer.create_task_group() block. If you need to access values of async tasks inside the same task group, you probably need a different approach, for example with AnyIO Streams.)
isinstancer/   r   r)   r!   r   r   r   value>   s
   zSoonValue.valuec                 C   s   t | jt S r-   )r1   r/   r   r!   r   r   r   readyJ   s   zSoonValue.readyr   N)	r$   r%   r&   r0   propertyr   r2   boolr3   r   r   r   r   r,   :   s    
r,   c                
       sP   e Zd Z	d	deeee f dedeeee f fddZ	d
 fddZ
  ZS )r   Nasync_functionnamer   c                    s4   t  dtjdtjdtt f fdd}|S )a
  
        Create and return a function that when called will start a new task in this
        task group.

        Internally it uses the same `task_group.start_soon()` method. But
        `task_group.soonify()` supports keyword arguments additional to positional
        arguments and it adds better support for autocompletion and inline errors
        for the arguments of the function called.

        Use it like this:

        ```Python
        async with asyncer.create_task_group() as task_group:
            async def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str:
                # Do work
                return "Some result value"

            result = task_group.soonify(do_work)("spam", "ham", kwarg1="a", kwarg2="b")

        print(result.value)
        ```

        The return value from that function (`result` in the example) is an object
        `SoonValue`.

        This `SoonValue` object has an attribute `soon_value.value` that will hold the
        return value of the original `async_function` *after* the `async with` block.

        If you try to access the `soon_value.value` inside the `async with` block,
        before it has the actual return value, it will raise a an exception
        `asyncer.PendingValueException`.

        If you think you need to access the return values inside the `async with` block,
        there's a high chance that you really need a different approach, for example
        using an AnyIO Stream.

        But either way, if you have checkpoints inside the `async with` block (you have
        some `await` there), one or more of the `SoonValue` objects you might have
        could end up having the result value ready before ending the `async with` block.
        You can check that with `soon_value.pending`. For example:

        ```Python
        async def do_work(name: str) -> str:
            return f"Hello {name}"

        async with asyncer.create_task_group() as task_group:
            result1 = task_group.soonify(do_work)(name="task 1")
            result2 = task_group.soonify(do_work)(name="task 2")
            await anyio.sleep(0)
            if not result1.pending:
                print(result1.value)
            if not result2.pending:
                print(result2.value)
        ```


        ## Arguments

        `async_function`: an async function to call soon
        `name`: name of the task, for the purposes of introspection and debugging

        ## Return

        A function that takes positional and keyword arguments and when called
        uses `task_group.start_soon()` to start the task in this task group.

        That function returns a `SoonValue` object holding the return value of the
        original function in `soon_value.value`.
        argskwargsr   c                     sJ   t jg| R i | t t  d fdd}j|d S )Nr   c                     s     I d H } | _ d S r-   )r/   )r2   	partial_f
soon_valuer   r   value_wrapper   s   
z9TaskGroup.soonify.<locals>.wrapper.<locals>.value_wrapper)r8   r4   )	functoolspartialr,   wraps
start_soon)r9   r:   r>   r7   r8   r"   r;   r   wrapper   s   z"TaskGroup.soonify.<locals>.wrapper)r?   rA   r   r9   r:   r,   r   )r"   r7   r8   rD   r   rC   r   soonifyP   s   IzTaskGroup.soonifyc                    s   t   I dH S )z:Enter the task group context and allow starting new tasks.N)super
__aenter__r!   	__class__r   r   rG      s   zTaskGroup.__aenter__r-   r   r   )r$   r%   r&   r   r   r   r   objectr,   rE   rG   __classcell__r   r   rH   r   r   O   s    
[r   c                  C   s    t  j} G dd d| t}| S )a  
    Create a task group used to start multiple concurrent tasks with async functions.

    `asyncer.create_task_group()` is different from `anyio.create_task_group()` in that
    it creates an extended `TaskGroup` object that includes the `task_group.soonify()`
    method.
    c                   @   r(   )z,create_task_group.<locals>.ExtendedTaskGroupNr*   r   r   r   r   ExtendedTaskGroup   r+   rM   )r   r   )LibTaskGrouprM   r   r   r   create_task_group   s   rO   asyncior7   backendbackend_optionsc                    s0   t  dtjdtjdtf fdd}|S )a~  
    Take an async function and create a regular (blocking) function that receives the
    same keyword and positional arguments for the original async function, and that when
    called will create an event loop and use it to run the original `async_function`
    with those arguments.

    That function returns the return value from the original `async_function`.

    The current thread must not be already running an event loop.

    This calls `anyio.run()` underneath.

    Use it like this:

    ```Python
    async def program(name: str) -> str:
        return f"Hello {name}"


    result = asyncer.runnify(program)(name="World")
    print(result)
    ```

    ## Arguments

    `async_function`: an async function to call
    `backend` name of the asynchronous event loop implementation - currently either
        `asyncio` or `trio`
    `backend_options` keyword arguments to call the backend `run()` implementation with

    ## Return

    The return value of the async function

    ## Raises

    `RuntimeError`: if an asynchronous event loop is already running in this thread
    `LookupError`: if the named backend is not found

    r9   r:   r   c                     s(   t j g| R i |}tj|dS )N)rQ   rR   )r?   r@   anyiorunr9   r:   r<   r7   rQ   rR   r   r   rD      s   zrunnify.<locals>.wrapperr?   rA   r   r9   r:   r   )r7   rQ   rR   rD   r   rV   r   runnify   s   .$rX   Traise_sync_errorc                    s.   t  dtjdtjdtf fdd}|S )a#  
    Take an async function and create a regular one that receives the same keyword and
    positional arguments, and that when called, calls the original async function in
    the main async loop from the worker thread using `anyio.to_thread.run()`.

    By default this is expected to be used from a worker thread. For example inside
    some function passed to `asyncify()`.

    But if you set `check_called_from_async` to `False`, you can also use this function
    in a non-async context: without an async event loop. For example, from a
    blocking/regular function called at the top level of a Python file. In that case,
    if it is not being called from inside a worker thread started from an async context
    (e.g. this is not called from a function that was called with `asyncify()`) it will
    run `async_function` in a new async event loop with `anyio.run()`.

    This functionality with `check_called_from_async` is there only to allow using
    `syncify()` in codebases that are used by async code in some cases and by blocking
    code in others. For example, during migrations from blocking code to async code.

    Internally, `asyncer.syncify()` uses the same `anyio.from_thread.run()`, but it
    supports keyword arguments additional to positional arguments and it adds better
    support for tooling (e.g. editor autocompletion and inline errors) for the
    arguments and return value of the function.

    Use it like this:

    ```Python
    async def do_work(arg1, arg2, kwarg1="", kwarg2=""):
        # Do work

    result = from_thread.syncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b")
    ```

    ## Arguments

    `async_function`: an async function to be called in the main thread, in the async
        event loop
    `check_called_from_async`: If set to `False`

    ## Return

    A regular blocking function that takes the same positional and keyword arguments
        as the original async one, that when called runs the same original function in
        the main async loop when called from a worker thread and returns the result.
    r9   r:   r   c                     sJ   t tdd }tj g| R i |}|d u rdu rt|S tj|S )Ncurrent_async_moduleF)getattrr   r?   r@   rS   rT   from_thread)r9   r:   rZ   r<   r7   rY   r   r   rD   )  s
   
zsyncify.<locals>.wrapperrW   )r7   rY   rD   r   r]   r   syncify   s   2"r^   Fcancellablelimiterfunctionr`   ra   c                   s&   dt jdt jdtf fdd}|S )a  
    Take a blocking function and create an async one that receives the same
    positional and keyword arguments, and that when called, calls the original function
    in a worker thread using `anyio.to_thread.run_sync()`. Internally,
    `asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports
    keyword arguments additional to positional arguments and it adds better support for
    autocompletion and inline errors for the arguments of the function called and the
    return value.


    If the `cancellable` option is enabled and the task waiting for its completion is
    cancelled, the thread will still run its course but its return value (or any raised
    exception) will be ignored.

    Use it like this:

    ```Python
    def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str:
            # Do work
            return "Some result"

    result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b")
    print(result)
    ```

    ## Arguments

    `function`: a blocking regular callable (e.g. a function)
    `cancellable`: `True` to allow cancellation of the operation
    `limiter`: capacity limiter to use to limit the total amount of threads running
        (if omitted, the default limiter is used)

    ## Return

    An async function that takes the same positional and keyword arguments as the
    original one, that when called runs the same original function in a thread worker
    and returns the result.

    r9   r:   r   c                     s2   t jg| R i |}tjj| dI d H S )Nr_   )r?   r@   rS   	to_threadrun_syncrU   r`   rb   ra   r   r   rD   b  s
   zasyncify.<locals>.wrapper)r   r9   r:   r   )rb   r`   ra   rD   r   re   r   asyncify4  s   .rf   r-   rJ   )rP   N)T)(r?   r   	importlibr   typingr   r   r   r   r   r   r	   r
   r   version_infor   typing_extensionsrS   r   anyio._core._eventloopr   	anyio.abcr   
_TaskGroupr'   r   r   r   r   r   r.   	Exceptionr)   r,   rO   rX   r6   r^   CapacityLimiterrf   r   r   r   r   <module>   sf    ,

a

9

@
