1212
1313# Standard
1414import copy
15+ import logging
16+ import weakref
1517from typing import Any , Iterator , Optional , TypeVar
1618
1719# Third-Party
1820from pydantic import BaseModel , RootModel
1921
22+ # First-Party
23+ from cpex .framework .errors import PluginFrameworkError
24+
2025T = TypeVar ("T" )
26+ logger = logging .getLogger (__name__ )
2127
2228
2329class CopyOnWriteDict (dict ):
@@ -517,6 +523,21 @@ def copyonwrite(o: T) -> T:
517523_PRIMITIVE_TYPES = (str , int , float , bool , bytes , type (None ))
518524
519525
526+ def _safe_deepcopy (value : Any ) -> Any :
527+ """Deep-copy *value* with diagnostic context on failure.
528+
529+ Wraps :func:`copy.deepcopy` so that failures surface the concrete type and
530+ a truncated repr, making it much easier to identify which field or object
531+ caused the issue (e.g. file handles, C extensions, locks).
532+ """
533+ try :
534+ return copy .deepcopy (value )
535+ except Exception as e :
536+ raise PluginFrameworkError (
537+ f"Cannot deep-copy value of type { type (value ).__qualname__ } (repr={ repr (value )!s:.200} ): { e } "
538+ ) from e
539+
540+
520541def _wrap_value (value : Any ) -> Any :
521542 """Wrap a single value with the appropriate CoW wrapper.
522543
@@ -536,16 +557,20 @@ def _wrap_value(value: Any) -> Any:
536557 elif isinstance (root , list ):
537558 wrapped_root = CopyOnWriteList (root )
538559 else :
539- wrapped_root = copy . deepcopy (root )
560+ wrapped_root = _safe_deepcopy (root )
540561 return value .model_construct (root = wrapped_root )
541562 if isinstance (value , BaseModel ):
542563 return wrap_payload_for_isolation (value )
543564 if isinstance (value , dict ):
544565 return CopyOnWriteDict (value )
545566 if isinstance (value , list ):
546567 return CopyOnWriteList (value )
568+ # Weak-reference proxies wrap live objects that must not be
569+ # copied — pass them through as-is so plugins see the same proxy.
570+ if isinstance (value , (weakref .ProxyType , weakref .CallableProxyType )):
571+ return value
547572 # Other mutable types — fallback to deep copy
548- return copy . deepcopy (value )
573+ return _safe_deepcopy (value )
549574
550575
551576def wrap_payload_for_isolation (payload : BaseModel ) -> BaseModel :
@@ -570,7 +595,7 @@ def wrap_payload_for_isolation(payload: BaseModel) -> BaseModel:
570595 elif isinstance (root , list ):
571596 wrapped_root = CopyOnWriteList (root )
572597 else :
573- wrapped_root = copy . deepcopy (root )
598+ wrapped_root = _safe_deepcopy (root )
574599 return payload .model_construct (root = wrapped_root )
575600
576601 updates = {}
0 commit comments