Skip to content

Commit 9f73c18

Browse files
brittanyreymeta-codesync[bot]
authored andcommitted
Override more pure-wrapper decorators as safe
Summary: More stubs + more tests Reviewed By: martindemello Differential Revision: D107561779 fbshipit-source-id: e3875869b34a3a364e057e66b1d002953e4185e1
1 parent 51e2005 commit 9f73c18

3 files changed

Lines changed: 104 additions & 1 deletion

File tree

resources/stubs/stdlib/functools.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ else:
148148
updated: Iterable[str] = ("__dict__",),
149149
) -> _Wrapper[_PWrapped, _RWrapped]: ...
150150

151-
def total_ordering(cls: type[_T]) -> type[_T]: ...
151+
def total_ordering(cls: type[_T]) -> type[_T]: no_effects()
152152
def cmp_to_key(mycmp: Callable[[_T, _T], int]) -> Callable[[_T], SupportsAllComparisons]: ...
153153
@disjoint_base
154154
class partial(Generic[_T]):

src/manual_override.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ const SAFE_FUNCTIONS_ARRAY: &[&str] = &[
2626
// Decorators for caching values. These are akin to functools.cache().
2727
"f3.utils.decorators.cache",
2828
"libfb.py.decorators.lazy_property",
29+
"libfb.py.decorators.memoize_fast",
30+
"libfb.py.decorators.memoize_fast_0",
2931
"libfb.py.decorators.memoize_forever",
3032
"libfb.py.decorators.memoize_timed",
33+
"libfb.py.memoize.memoize_fast",
34+
"libfb.py.memoize.memoize_fast_0",
35+
"psutil._common.memoize",
36+
"psutil._common.memoize_when_activated",
3137
// Functions that use the ABCMeta registry, which is hard to analyze but it is safe.
3238
"collections.abc.Mapping.register",
3339
"collections.abc.Sequence.register",
@@ -106,10 +112,14 @@ const SAFE_FUNCTIONS_ARRAY: &[&str] = &[
106112
"dns.name.IDNA2003Codec",
107113
"dns.name.IDNA2008Codec",
108114
"dns.name.Name",
115+
"google.auth._helpers.copy_docstring",
109116
// Pure-wrapper deprecation decorators. At decoration time they only build a
110117
// closure that emits a DeprecationWarning at call time; no module-level
111118
// registry, no I/O. Same shape as langchain_core._api.deprecation.deprecated.
112119
"markdown.util.deprecated",
120+
// Pure functools.wraps wrapper. All tracing work happens inside the async
121+
// wrapper at call time, never at decoration time.
122+
"model_context_protocol.common.decorators.mcp_client_connect_decorator.mcp_client_connect",
113123
"nltk.internals.deprecated",
114124
"sympy.utilities.decorator.deprecated",
115125
// sandcastle test gate: at decoration time it either returns the function

tests/stubs.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,99 @@ def f(new=1):
182182
check(code);
183183
}
184184

185+
#[test]
186+
fn test_libfb_memoize_fast_safe() {
187+
// Regression test for the libfb memoize_fast / memoize_fast_0 overrides
188+
// in manual_override.rs. Both build a cache-holding closure and return it
189+
// at decoration time; the wrapped function only runs on first call.
190+
let code = r#"
191+
from libfb.py.decorators import memoize_fast, memoize_fast_0
192+
193+
@memoize_fast
194+
def f(key):
195+
return key
196+
197+
@memoize_fast_0
198+
def g():
199+
return 1
200+
"#;
201+
check(code);
202+
}
203+
204+
#[test]
205+
fn test_psutil_memoize_safe() {
206+
// Regression test for the psutil memoize / memoize_when_activated overrides
207+
// in manual_override.rs. Both wrap via functools.wraps and cache at call
208+
// time; nothing mutates module state at decoration time.
209+
let code = r#"
210+
from psutil._common import memoize, memoize_when_activated
211+
212+
@memoize
213+
def f(x):
214+
return x
215+
216+
@memoize_when_activated
217+
def g(self):
218+
return 1
219+
"#;
220+
check(code);
221+
}
222+
223+
#[test]
224+
fn test_google_auth_copy_docstring_safe() {
225+
// Regression test for the google.auth._helpers.copy_docstring override in
226+
// manual_override.rs. It returns a closure that copies a docstring onto
227+
// the decorated method only, with no module-level side effects.
228+
let code = r#"
229+
from google.auth._helpers import copy_docstring
230+
231+
class Base:
232+
def m(self):
233+
"doc"
234+
235+
class C:
236+
@copy_docstring(Base)
237+
def m(self):
238+
return 1
239+
"#;
240+
check(code);
241+
}
242+
243+
#[test]
244+
fn test_functools_total_ordering_safe() {
245+
// Regression test for the no_effects() annotation on functools.total_ordering
246+
// in the bundled stdlib stub. It only injects comparison methods onto the
247+
// decorated class, with no module-scope side effects.
248+
let code = r#"
249+
from functools import total_ordering
250+
251+
@total_ordering
252+
class C:
253+
def __eq__(self, other):
254+
return True
255+
256+
def __lt__(self, other):
257+
return False
258+
"#;
259+
check(code);
260+
}
261+
262+
#[test]
263+
fn test_mcp_client_connect_safe() {
264+
// Regression test for the mcp_client_connect override in manual_override.rs.
265+
// It is a pure functools.wraps wrapper; all tracing work happens inside the
266+
// async wrapper at call time, never at decoration time.
267+
let code = r#"
268+
from model_context_protocol.common.decorators.mcp_client_connect_decorator import mcp_client_connect
269+
270+
class C:
271+
@mcp_client_connect
272+
async def connect_to_server(self, config):
273+
return None
274+
"#;
275+
check(code);
276+
}
277+
185278
#[test]
186279
fn test_pydantic_stub_class_body_calls_safe() {
187280
// Regression test for the bundled pydantic/__init__.pyi stub.

0 commit comments

Comments
 (0)