commit:     6d8238e215722385ae2781bd184eb88d02996e3e
Author:     Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Thu Oct 23 17:38:04 2025 +0000
Commit:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Fri Oct 24 08:56:10 2025 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=6d8238e2

feat: make WeakInstMeta preserve __init__ annotations

For pydoc pkgcore.ebuild.atom, this results in this change:

before:
    class atom(pkgcore.restrictions.boolean.AndRestriction)
     |  atom(*a, **kw)

after:
    class atom(pkgcore.restrictions.boolean.AndRestriction)
     |  atom(atom: str, negate_vers: bool = False, eapi: str = '-1')

Whilst I'm in here, also clean up this code to use modern syntax
and be saner to read.

Signed-off-by: Brian Harring <ferringb <AT> gmail.com>
Closes: https://github.com/pkgcore/snakeoil/pull/106
Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>

 src/snakeoil/caching.py | 77 ++++++++++++++++++++++++++-----------------------
 tests/test_caching.py   | 20 +++++++++++++
 2 files changed, 61 insertions(+), 36 deletions(-)

diff --git a/src/snakeoil/caching.py b/src/snakeoil/caching.py
index b20f074..ab3f2f3 100644
--- a/src/snakeoil/caching.py
+++ b/src/snakeoil/caching.py
@@ -54,6 +54,7 @@ Simple usage example:
 
 __all__ = ("WeakInstMeta",)
 
+import functools
 import warnings
 from weakref import WeakValueDictionary
 
@@ -80,42 +81,46 @@ class WeakInstMeta(type):
     U{pkgcore project<http://pkgcore.org>}
     """
 
-    def __new__(cls, name, bases, d):
-        if d.get("__inst_caching__", False):
-            d["__inst_caching__"] = True
-            d["__inst_dict__"] = WeakValueDictionary()
-        else:
-            d["__inst_caching__"] = False
-        slots = d.get("__slots__")
-        # get ourselves a singleton to be safe...
-        o = object()
-        if slots is not None:
-            for base in bases:
-                if getattr(base, "__weakref__", o) is not o:
-                    break
-            else:
-                d["__slots__"] = tuple(slots) + ("__weakref__",)
-        return type.__new__(cls, name, bases, d)
+    def __new__(cls, name: str, bases: tuple[type, ...], scope) -> type:
+        if scope.setdefault("__inst_caching__", False):
+            scope["__inst_dict__"] = WeakValueDictionary()
+            if new_init := scope.get("__init__"):
+                # derive a new metaclass on the fly so we can ensure the 
__call__
+                # signature carries the docs/annotations of the __init__ for 
this class.
+                # Basically, show the __init__ doc/annotations, rather than 
just the bare
+                # doc/annotations of cls.__call__
+                class c(cls):
+                    __call__ = functools.wraps(new_init)(cls.__call__)
+                    # wraps passes the original annotations; don't mutate the 
underlying __init__
+                    __call__.__annotations__ = new_init.__annotations__.copy()
+                    __call__.__annotations__["disable_inst_caching"] = bool
+
+                c.__name__ = f"_{cls.__name__}_{name}"
+                cls = c
+
+        # if slots is in use, no __weakref__ slot disables weakref; inject it 
if needed.
+        if (slots := scope.get("__slots__")) is not None:
+            if not any(hasattr(base, "__weakref__") for base in bases):
+                scope["__slots__"] = tuple(slots) + ("__weakref__",)
+
+        return type.__new__(cls, name, bases, scope)
 
     def __call__(cls, *a, **kw):
         """disable caching via disable_inst_caching=True"""
-        if cls.__inst_caching__ and not kw.pop("disable_inst_caching", False):
-            kwlist = list(kw.items())
-            kwlist.sort()
-            key = (a, tuple(kwlist))
-            try:
-                instance = cls.__inst_dict__.get(key)
-            except (NotImplementedError, TypeError) as t:
-                warnings.warn(f"caching keys for {cls}, got {t} for a={a}, 
kw={kw}")
-                del t
-                key = instance = None
-
-            if instance is None:
-                instance = super(WeakInstMeta, cls).__call__(*a, **kw)
-
-                if key is not None:
-                    cls.__inst_dict__[key] = instance
-        else:
-            instance = super(WeakInstMeta, cls).__call__(*a, **kw)
-
-        return instance
+        # This is subtle, but note that this explictly passes 
"disable_inst_caching" down to the class
+        # if the class itself has disabled caching.  This is a debatable 
design- it means any
+        # consumer that disables caching across a semver will throw an 
exception here.  However,
+        # this is historical behavior, thus left this way.
+        if not cls.__inst_caching__ or kw.pop("disable_inst_caching", False):  
# type: ignore[attr-defined]
+            return super(WeakInstMeta, cls).__call__(*a, **kw)
+
+        try:
+            key = (a, tuple(sorted(kw.items())))
+            if None is (instance := cls.__inst_dict__.get(key)):  # type: 
ignore[attr-defined]
+                instance = cls.__inst_dict__[key] = super(WeakInstMeta, 
cls).__call__(
+                    *a, **kw
+                )  # type: ignore[attr-defined]
+            return instance
+        except (NotImplementedError, TypeError) as t:
+            warnings.warn(f"caching keys for {cls}, got {t} for a={a}, 
kw={kw}")
+            return super(WeakInstMeta, cls).__call__(*a, **kw)

diff --git a/tests/test_caching.py b/tests/test_caching.py
index 06615d3..5524c8b 100644
--- a/tests/test_caching.py
+++ b/tests/test_caching.py
@@ -169,3 +169,23 @@ class TestWeakInstMeta:
         gc.collect()
         o = weak_inst(unique)
         assert weak_inst.counter == 2
+
+    def test_function_metadata(self):
+        def f(
+            self, x: str, y: int = 1, *a: tuple[str, ...], **kw: int
+        ) -> dict[str, int]:
+            "blah blah blah blah"
+            return {}
+
+        class c(metaclass=WeakInstMeta):
+            __inst_caching__ = True
+            __init__ = f  # type: ignore
+
+        new_call = c.__class__.__call__
+        # ensure it's not mutating the reference functools passes.
+        assert new_call.__annotations__ is not f.__annotations__
+        assert new_call.__annotations__["disable_inst_caching"] == bool
+        a = new_call.__annotations__.copy()
+        a.pop("disable_inst_caching")
+        assert a == f.__annotations__
+        assert c.__init__.__doc__ == f.__doc__

Reply via email to