<commit_before>"""A module exploring some python asyncio interactions."""


# [ Imports ]
# [ -Python ]
import asyncio
import functools
import contextlib
from typing import Generator, Callable, Any, List, Union
from concurrent import futures
# [ -Project ]
import helpers


# [ Types ]
# convert AnyCallable to just Callable when https://github.com/python/mypy/issues/1484 is resolved
AnyCallable = Union[Callable, functools.partial]


# [ API Functions ]
@contextlib.contextmanager
def idle_event_loop() -> Generator:
    """
    An idle event loop context manager.

    Leaves the current event loop in place if it's not running.
    Creates and sets a new event loop as the default if the current default IS running.
    Restores the original event loop on exit.

    Args:
        None

    Returns:
        idle_event_loop_manager - a context manager which yields an idle event loop
          for you to run async functions in.
    """
    original_loop = helpers.get_or_create_event_loop()
    try:
        if original_loop.is_running():
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
        else:
            loop = original_loop
        yield loop
    finally:
        asyncio.set_event_loop(original_loop)


def to_async(blocking_func: AnyCallable) -> AnyCallable:
    """
    Convert a blocking function to an async function.

    Args:
        blocking_func - a blocking function

    Returns:
        async_func - an awaitable function

    If the argument is already a coroutine function, it is returned unchanged.

    If the argument is a partial which wraps a coroutine function, it is returned
    unchanged.

    The blocking function is made into an awaitable by wrapping its call inside
    an awaitable function.

    On the assumption that a blocking function is long-running, and that an
    async caller wants to run other things concurrently, the blocking function
    is called from the running loop's thread/process executor, which means
    it runs in a thread.  This allows concurrent running, but introduces possible
    thread-safety issues, which the caller should be aware of.

    XXX - what if the loop isn't running?  We could be in someone else's loop
    space, like the curio kernel.  The 'run_in_executor' function returns a
    coroutine, so it shouldn't matter.

    XXX - possible new function - 'to_concurrent', which does the thread. Update
    this to 'to_awaitable', which just wraps in an awaitable.

    XXX - or, 'to_awaitable' for awaitable wrapping, 'to_concurrent' for thread,
    and 'to_parallel' for multiprocess?  Too much?  Wait for a need?  Want to not
    break code if we add these later, so the current version should probably be
    renamed to 'to_concurrent'.

    Required tests:
        * convert blocking to async
        * keep async as same
        * run something concurrently with a long-running blocking function
        * convert a blocking partial
        * keep an async partial the same
        * validate that it doesn't matter if the loop is running.
    """
    if asyncio.iscoroutinefunction(getattr(blocking_func, 'func', blocking_func)):
        # caller messed up - this is already async
        async_func = blocking_func
    else:
        async def async_func(*args, **kwargs) -> Any:
            """Wrapper for a blocking function."""
            # remove ignore comment when https://github.com/python/mypy/issues/1484 is resolved
            partial = functools.partial(blocking_func, *args, **kwargs)  # type: ignore
            loop = asyncio.get_event_loop()
            # lambda is to get around AbstractEventLoop typing not accepting partials
            # remove lambda when https://github.com/python/mypy/issues/1484 is resolved
            return await loop.run_in_executor(helpers.get_or_create_executor(), lambda: partial)
    return async_func


def to_blocking(async_func: AnyCallable) -> AnyCallable:
    """
    Convert an async function to a blocking function.

    Args:
        async_func - an async function

    Returns:
        blocking_func - a blocking function<commit_msg>fixup for pr #24, which broke to_async.<commit_after>"""A module exploring some python asyncio interactions."""


# [ Imports ]
# [ -Python ]
import asyncio
import functools
import contextlib
from typing import Generator, Callable, Any, List, Union
from concurrent import futures
# [ -Project ]
import helpers


# [ Types ]
# convert AnyCallable to just Callable when https://github.com/python/mypy/issues/1484 is resolved
AnyCallable = Union[Callable, functools.partial]


# [ API Functions ]
@contextlib.contextmanager
def idle_event_loop() -> Generator:
    """
    An idle event loop context manager.

    Leaves the current event loop in place if it's not running.
    Creates and sets a new event loop as the default if the current default IS running.
    Restores the original event loop on exit.

    Args:
        None

    Returns:
        idle_event_loop_manager - a context manager which yields an idle event loop
          for you to run async functions in.
    """
    original_loop = helpers.get_or_create_event_loop()
    try:
        if original_loop.is_running():
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
        else:
            loop = original_loop
        yield loop
    finally:
        asyncio.set_event_loop(original_loop)


def to_async(blocking_func: AnyCallable) -> AnyCallable:
    """
    Convert a blocking function to an async function.

    Args:
        blocking_func - a blocking function

    Returns:
        async_func - an awaitable function

    If the argument is already a coroutine function, it is returned unchanged.

    If the argument is a partial which wraps a coroutine function, it is returned
    unchanged.

    The blocking function is made into an awaitable by wrapping its call inside
    an awaitable function.

    On the assumption that a blocking function is long-running, and that an
    async caller wants to run other things concurrently, the blocking function
    is called from the running loop's thread/process executor, which means
    it runs in a thread.  This allows concurrent running, but introduces possible
    thread-safety issues, which the caller should be aware of.

    XXX - what if the loop isn't running?  We could be in someone else's loop
    space, like the curio kernel.  The 'run_in_executor' function returns a
    coroutine, so it shouldn't matter.

    XXX - possible new function - 'to_concurrent', which does the thread. Update
    this to 'to_awaitable', which just wraps in an awaitable.

    XXX - or, 'to_awaitable' for awaitable wrapping, 'to_concurrent' for thread,
    and 'to_parallel' for multiprocess?  Too much?  Wait for a need?  Want to not
    break code if we add these later, so the current version should probably be
    renamed to 'to_concurrent'.

    Required tests:
        * convert blocking to async
        * keep async as same
        * run something concurrently with a long-running blocking function
        * convert a blocking partial
        * keep an async partial the same
        * validate that it doesn't matter if the loop is running.
    """
    if asyncio.iscoroutinefunction(getattr(blocking_func, 'func', blocking_func)):
        # caller messed up - this is already async
        async_func = blocking_func
    else:
        async def async_func(*args, **kwargs) -> Any:
            """Wrapper for a blocking function."""
            # remove ignore comment when https://github.com/python/mypy/issues/1484 is resolved
            partial = functools.partial(blocking_func, *args, **kwargs)  # type: ignore
            loop = asyncio.get_event_loop()
            # lambda is to get around AbstractEventLoop typing not accepting partials
            # remove lambda when https://github.com/python/mypy/issues/1484 is resolved
            return await loop.run_in_executor(helpers.get_or_create_executor(), lambda: partial())
    return async_func


def to_blocking(async_func: AnyCallable) -> AnyCallable:
    """
    Convert an async function to a blocking function.

    Args:
        async_func - an async function

    Returns:
        blocking_func - a blocking function
{'fraction_non_alphanumeric': 0.06251511487303507, 'fraction_numerical': 0.003143893591293833, 'mean_word_length': 2.875820056232427, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7333270', 'n_tokens_mistral': 2235, 'n_tokens_neox': 2143, 'n_words': 909}