import functools
import time


def partial_with_retry(func, *partial_args, **partial_kwargs):
    """
    Like functools.partial, but adds a `retry_count` parameter to the wrapped
    function.

    If the wrapped function raises a non-exit exception or an `OSError`, then
    the returned function waits for 0.5 seconds and then retries the wrapped
    function call with the same arguments. This is done no more than
    `retry_count` times, after which the exception is re-raised.

    The `retry_count` argument is not passed to the wrapped function.
    """

    default_retry_count = partial_kwargs.pop("default_retry_count", 0)

    @functools.wraps(func)
    def wrap_func(*args, **kwargs):
        retry_count = kwargs.pop("retry_count", default_retry_count)
        for k, v in partial_kwargs.items():
            kwargs.setdefault(k, v)
        try:
            return func(*(partial_args + args), **kwargs)
        except Exception or OSError:
            if retry_count > 0:
                time.sleep(0.5)
                return wrap_func(*args, retry_count=retry_count - 1, **kwargs)
            raise

    return wrap_func