From fc5b5cf34171ee66944dce98fcc50f236dc49192 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:50:51 +0200 Subject: [PATCH 1/2] Preserve async deprecation wrappers --- src/pyrecest/deprecation.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/pyrecest/deprecation.py b/src/pyrecest/deprecation.py index 69400f3ecd..1075bd1897 100644 --- a/src/pyrecest/deprecation.py +++ b/src/pyrecest/deprecation.py @@ -3,6 +3,7 @@ from __future__ import annotations import functools +import inspect import warnings from collections.abc import Callable from typing import ParamSpec, TypeVar @@ -48,6 +49,18 @@ def decorator(func: Callable[P, R]) -> Callable[P, R]: if replacement: message += f" Use {replacement} instead." + if inspect.iscoroutinefunction(func): + + @functools.wraps(func) + async def async_wrapper(*args: P.args, **kwargs: P.kwargs): + warnings.warn(message, DeprecationWarning, stacklevel=2) + return await func(*args, **kwargs) + + async_wrapper.__deprecated_since__ = since # type: ignore[attr-defined] + async_wrapper.__deprecated_remove_in__ = remove_in # type: ignore[attr-defined] + async_wrapper.__deprecated_replacement__ = replacement # type: ignore[attr-defined] + return async_wrapper # type: ignore[return-value] + @functools.wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: warnings.warn(message, DeprecationWarning, stacklevel=2) From 328198689103e63db78c45cbb81f4b88dafe936e Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:51:11 +0200 Subject: [PATCH 2/2] Test async deprecation wrappers --- tests/test_deprecation_helper.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_deprecation_helper.py b/tests/test_deprecation_helper.py index 7624e6e810..d1a8222517 100644 --- a/tests/test_deprecation_helper.py +++ b/tests/test_deprecation_helper.py @@ -1,3 +1,5 @@ +import asyncio +import inspect import warnings import pytest @@ -18,6 +20,21 @@ def legacy_function(): assert "new_function" in str(caught[0].message) +def test_deprecated_decorator_preserves_async_function_contract(): + @deprecated(since="2.3.0", remove_in="3.0.0", replacement="new_async_function") + async def legacy_async_function(value): + return value + 1 + + assert inspect.iscoroutinefunction(legacy_async_function) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + assert asyncio.run(legacy_async_function(1)) == 2 + + assert len(caught) == 1 + assert issubclass(caught[0].category, DeprecationWarning) + assert "new_async_function" in str(caught[0].message) + + def test_deprecated_decorator_rejects_blank_since(): with pytest.raises(ValueError, match="since must be a non-empty string"): deprecated(since=" ", remove_in="3.0.0")