mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-01 14:05:10 +02:00
124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
import sys
|
|
import types
|
|
from typing import Any, Callable, Optional
|
|
|
|
from langgraph.constants import RETURN
|
|
from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry
|
|
from langgraph.utils.runnable import RunnableSeq, coerce_to_runnable
|
|
|
|
"""
|
|
Utilities borrowed from cloudpickle.
|
|
https://github.com/cloudpipe/cloudpickle/blob/6220b0ce83ffee5e47e06770a1ee38ca9e47c850/cloudpickle/cloudpickle.py#L265
|
|
"""
|
|
|
|
|
|
def _getattribute(obj: Any, name: str) -> Any:
|
|
for subpath in name.split("."):
|
|
if subpath == "<locals>":
|
|
raise AttributeError(
|
|
"Can't get local attribute {!r} on {!r}".format(name, obj)
|
|
)
|
|
try:
|
|
parent = obj
|
|
obj = getattr(obj, subpath)
|
|
except AttributeError:
|
|
raise AttributeError(
|
|
"Can't get attribute {!r} on {!r}".format(name, obj)
|
|
) from None
|
|
return obj, parent
|
|
|
|
|
|
def _whichmodule(obj: Any, name: str) -> Optional[str]:
|
|
"""Find the module an object belongs to.
|
|
|
|
This function differs from ``pickle.whichmodule`` in two ways:
|
|
- it does not mangle the cases where obj's module is __main__ and obj was
|
|
not found in any module.
|
|
- Errors arising during module introspection are ignored, as those errors
|
|
are considered unwanted side effects.
|
|
"""
|
|
module_name = getattr(obj, "__module__", None)
|
|
|
|
if module_name is not None:
|
|
return module_name
|
|
# Protect the iteration by using a copy of sys.modules against dynamic
|
|
# modules that trigger imports of other modules upon calls to getattr or
|
|
# other threads importing at the same time.
|
|
for module_name, module in sys.modules.copy().items():
|
|
# Some modules such as coverage can inject non-module objects inside
|
|
# sys.modules
|
|
if (
|
|
module_name == "__main__"
|
|
or module_name == "__mp_main__"
|
|
or module is None
|
|
or not isinstance(module, types.ModuleType)
|
|
):
|
|
continue
|
|
try:
|
|
if _getattribute(module, name)[0] is obj:
|
|
return module_name
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
|
|
def _lookup_module_and_qualname(
|
|
obj: Any, name: Optional[str] = None
|
|
) -> Optional[tuple[types.ModuleType, str]]:
|
|
if name is None:
|
|
name = getattr(obj, "__qualname__", None)
|
|
if name is None: # pragma: no cover
|
|
# This used to be needed for Python 2.7 support but is probably not
|
|
# needed anymore. However we keep the __name__ introspection in case
|
|
# users of cloudpickle rely on this old behavior for unknown reasons.
|
|
name = getattr(obj, "__name__", None)
|
|
if name is None:
|
|
return None
|
|
|
|
module_name = _whichmodule(obj, name)
|
|
|
|
if module_name is None:
|
|
# In this case, obj.__module__ is None AND obj was not found in any
|
|
# imported module. obj is thus treated as dynamic.
|
|
return None
|
|
|
|
if module_name == "__main__":
|
|
return None
|
|
|
|
# Note: if module_name is in sys.modules, the corresponding module is
|
|
# assumed importable at unpickling time. See #357
|
|
module = sys.modules.get(module_name, None)
|
|
if module is None:
|
|
# The main reason why obj's module would not be imported is that this
|
|
# module has been dynamically created, using for example
|
|
# types.ModuleType. The other possibility is that module was removed
|
|
# from sys.modules after obj was created/imported. But this case is not
|
|
# supported, as the standard pickle does not support it either.
|
|
return None
|
|
|
|
try:
|
|
obj2, parent = _getattribute(module, name)
|
|
except AttributeError:
|
|
# obj was not found inside the module it points to
|
|
return None
|
|
if obj2 is not obj:
|
|
return None
|
|
return module, name
|
|
|
|
|
|
def get_runnable_for_func(func: Callable[..., Any]) -> RunnableSeq:
|
|
if func in CACHE:
|
|
return CACHE[func]
|
|
else:
|
|
seq = RunnableSeq(
|
|
coerce_to_runnable(func, name=None, trace=False),
|
|
ChannelWrite([ChannelWriteEntry(RETURN)]),
|
|
name=func.__name__,
|
|
)
|
|
if not _lookup_module_and_qualname(func):
|
|
return seq
|
|
return CACHE.setdefault(func, seq)
|
|
|
|
|
|
CACHE: dict[Callable[..., Any], RunnableSeq] = {}
|