Skip to content

Commit 322be0a

Browse files
committed
Preserve enableable signature metadata
1 parent e08cbc4 commit 322be0a

8 files changed

Lines changed: 836 additions & 403 deletions

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ classifiers = [
2626
"Topic :: Utilities",
2727
]
2828

29-
dependencies = []
29+
dependencies = [
30+
"metaclass-registry>=0.1.0",
31+
]
3032

3133
[project.optional-dependencies]
3234
dev = [

src/python_introspect/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
# Plugin registration
2020
register_namespace_provider,
2121
register_type_resolver,
22+
set_signature_analysis_target,
23+
signature_analysis_target,
2224
)
2325
from .unified_parameter_analyzer import (
2426
UnifiedParameterAnalyzer,
2527
UnifiedParameterInfo,
28+
set_parameter_exclusions,
29+
parameter_exclusions,
2630
)
2731
from .exceptions import (
2832
IntrospectionError,
@@ -48,9 +52,13 @@
4852
# Plugin registration
4953
"register_namespace_provider",
5054
"register_type_resolver",
55+
"set_signature_analysis_target",
56+
"signature_analysis_target",
5157
# Unified analysis
5258
"UnifiedParameterAnalyzer",
5359
"UnifiedParameterInfo",
60+
"set_parameter_exclusions",
61+
"parameter_exclusions",
5462
# Exceptions
5563
"IntrospectionError",
5664
"SignatureAnalysisError",

src/python_introspect/enableable.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,41 @@
1515
from abc import ABC, ABCMeta
1616
from dataclasses import dataclass
1717
from typing import Any
18+
from weakref import WeakKeyDictionary
1819

1920

2021
ENABLED_FIELD = 'enabled'
2122

2223
_ENABLEABLE_TAG = object()
24+
_enableable_objects: WeakKeyDictionary[Any, object] = WeakKeyDictionary()
25+
_enableable_objects_by_id: dict[int, tuple[Any, object]] = {}
26+
27+
28+
def _remember_enableable(obj: Any) -> None:
29+
"""Record explicit enableable branding without mutating the object."""
30+
try:
31+
_enableable_objects[obj] = _ENABLEABLE_TAG
32+
except TypeError:
33+
_enableable_objects_by_id[id(obj)] = (obj, _ENABLEABLE_TAG)
34+
35+
36+
def _is_marked_enableable(obj: Any) -> bool:
37+
"""Return whether an object was explicitly branded as enableable."""
38+
try:
39+
if _enableable_objects.get(obj) is _ENABLEABLE_TAG:
40+
return True
41+
except TypeError:
42+
pass
43+
44+
fallback_record = _enableable_objects_by_id.get(id(obj))
45+
return fallback_record is not None and fallback_record[0] is obj
2346

2447

2548
class EnableableMeta(ABCMeta):
2649
"""Metaclass enabling nominal isinstance checks for branded callables."""
2750

2851
def __instancecheck__(cls, instance: Any) -> bool: # type: ignore[override]
29-
if getattr(instance, '__enableable_tag__', None) is _ENABLEABLE_TAG:
52+
if _is_marked_enableable(instance):
3053
return True
3154
return super().__instancecheck__(instance)
3255

@@ -72,8 +95,8 @@ def mark_enableable(obj: Any, *, enabled_default: bool = True) -> Any:
7295
sig = inspect.signature(obj)
7396
if ENABLED_FIELD not in sig.parameters:
7497
raise TypeError(
75-
f"Enableable callable '{getattr(obj, '__name__', obj)}' must have an '{ENABLED_FIELD}' parameter"
98+
f"Enableable callable {obj!r} must have an '{ENABLED_FIELD}' parameter"
7699
)
77100

78-
setattr(obj, '__enableable_tag__', _ENABLEABLE_TAG)
101+
_remember_enableable(obj)
79102
return obj

0 commit comments

Comments
 (0)