mirror of https://github.com/hwchase17/langchain
langchain[minor]: Code to handle dynamic imports (#20893)
Proposing to centralize code for handling dynamic imports. This allows treating langchain-community as an optional dependency. --- The proposal is to scan the code base and to replace all existing imports with dynamic imports using this functionality.pull/21144/head
parent
854ae3e1de
commit
82d4afcac0
@ -0,0 +1,128 @@
|
|||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import warnings
|
||||||
|
from typing import Any, Callable, Dict, Optional
|
||||||
|
|
||||||
|
from langchain_core._api import LangChainDeprecationWarning
|
||||||
|
|
||||||
|
from langchain.utils.interactive_env import is_interactive_env
|
||||||
|
|
||||||
|
ALLOWED_TOP_LEVEL_PKGS = {
|
||||||
|
"langchain_community",
|
||||||
|
"langchain_core",
|
||||||
|
"langchain",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HERE = pathlib.Path(__file__).parent
|
||||||
|
ROOT = HERE.parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def _get_current_module(path: str) -> str:
|
||||||
|
"""Convert a path to a module name."""
|
||||||
|
path_as_pathlib = pathlib.Path(os.path.abspath(path))
|
||||||
|
relative_path = path_as_pathlib.relative_to(ROOT).with_suffix("")
|
||||||
|
posix_path = relative_path.as_posix()
|
||||||
|
norm_path = os.path.normpath(str(posix_path))
|
||||||
|
fully_qualified_module = norm_path.replace("/", ".")
|
||||||
|
# Strip off __init__ if present
|
||||||
|
if fully_qualified_module.endswith(".__init__"):
|
||||||
|
return fully_qualified_module[:-9]
|
||||||
|
return fully_qualified_module
|
||||||
|
|
||||||
|
|
||||||
|
def create_importer(
|
||||||
|
here: str,
|
||||||
|
*,
|
||||||
|
module_lookup: Optional[Dict[str, str]] = None,
|
||||||
|
fallback_module: Optional[str] = None,
|
||||||
|
) -> Callable[[str], Any]:
|
||||||
|
"""Create a function that helps retrieve objects from their new locations.
|
||||||
|
|
||||||
|
The goal of this function is to help users transition from deprecated
|
||||||
|
imports to new imports.
|
||||||
|
|
||||||
|
This function will raise warnings when the old imports are used and
|
||||||
|
suggest the new imports.
|
||||||
|
|
||||||
|
This function should ideally only be used with deprecated imports not with
|
||||||
|
existing imports that are valid, as in addition to raising deprecation warnings
|
||||||
|
the dynamic imports can create other issues for developers (e.g.,
|
||||||
|
loss of type information, IDE support for going to definition etc).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
here: path of the current file. Use __file__
|
||||||
|
module_lookup: maps name of object to the module where it is defined.
|
||||||
|
e.g.,
|
||||||
|
{
|
||||||
|
"MyDocumentLoader": (
|
||||||
|
"langchain_community.document_loaders.my_document_loader"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fallback_module: module to import from if the object is not found in
|
||||||
|
module_lookup or if module_lookup is not provided.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A function that imports objects from the specified modules.
|
||||||
|
"""
|
||||||
|
current_module = _get_current_module(here)
|
||||||
|
|
||||||
|
def import_by_name(name: str) -> Any:
|
||||||
|
"""Import stores from langchain_community."""
|
||||||
|
# If not in interactive env, raise warning.
|
||||||
|
if module_lookup and name in module_lookup:
|
||||||
|
new_module = module_lookup[name]
|
||||||
|
if new_module.split(".")[0] not in ALLOWED_TOP_LEVEL_PKGS:
|
||||||
|
raise AssertionError(
|
||||||
|
f"Importing from {new_module} is not allowed. "
|
||||||
|
f"Allowed top-level packages are: {ALLOWED_TOP_LEVEL_PKGS}"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(new_module)
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
if new_module.startswith("langchain_community"):
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
f"Module {new_module} not found. "
|
||||||
|
"Please install langchain-community to access this module. "
|
||||||
|
"You can install it using `pip install -U langchain-community`"
|
||||||
|
) from e
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = getattr(module, name)
|
||||||
|
if not is_interactive_env():
|
||||||
|
warnings.warn(
|
||||||
|
f"Importing {name} from {current_module} is deprecated. "
|
||||||
|
"Please replace the import with the following:\n"
|
||||||
|
f"from {new_module} import {name}",
|
||||||
|
category=LangChainDeprecationWarning,
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
raise AttributeError(
|
||||||
|
f"module {new_module} has no attribute {name}"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
if fallback_module:
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(fallback_module)
|
||||||
|
result = getattr(module, name)
|
||||||
|
if not is_interactive_env():
|
||||||
|
warnings.warn(
|
||||||
|
f"Importing {name} from {current_module} is deprecated. "
|
||||||
|
"Please replace the import with the following:\n"
|
||||||
|
f"from {fallback_module} import {name}",
|
||||||
|
category=LangChainDeprecationWarning,
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise AttributeError(
|
||||||
|
f"module {fallback_module} has no attribute {name}"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
raise AttributeError(f"module {current_module} has no attribute {name}")
|
||||||
|
|
||||||
|
return import_by_name
|
@ -1,6 +1,32 @@
|
|||||||
from langchain_community.chat_loaders.facebook_messenger import (
|
from typing import TYPE_CHECKING, Any
|
||||||
FolderFacebookMessengerChatLoader,
|
|
||||||
SingleFileFacebookMessengerChatLoader,
|
from langchain._api.module_import import create_importer
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langchain_community.chat_loaders.facebook_messenger import (
|
||||||
|
FolderFacebookMessengerChatLoader,
|
||||||
|
SingleFileFacebookMessengerChatLoader,
|
||||||
|
)
|
||||||
|
|
||||||
|
module_lookup = {
|
||||||
|
"SingleFileFacebookMessengerChatLoader": (
|
||||||
|
"langchain_community.chat_loaders.facebook_messenger"
|
||||||
|
),
|
||||||
|
"FolderFacebookMessengerChatLoader": (
|
||||||
|
"langchain_community.chat_loaders.facebook_messenger"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Temporary code for backwards compatibility for deprecated imports.
|
||||||
|
# This will eventually be removed.
|
||||||
|
import_lookup = create_importer(
|
||||||
|
__file__,
|
||||||
|
module_lookup=module_lookup,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(name: str) -> Any:
|
||||||
|
return import_lookup(name)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["SingleFileFacebookMessengerChatLoader", "FolderFacebookMessengerChatLoader"]
|
__all__ = ["SingleFileFacebookMessengerChatLoader", "FolderFacebookMessengerChatLoader"]
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
from langchain._api.module_import import create_importer
|
||||||
|
|
||||||
|
|
||||||
|
def test_importing_module() -> None:
|
||||||
|
"""Test importing all modules in langchain."""
|
||||||
|
module_lookup = {
|
||||||
|
"Document": "langchain_core.documents",
|
||||||
|
}
|
||||||
|
lookup = create_importer(__file__, module_lookup=module_lookup)
|
||||||
|
imported_doc = lookup("Document")
|
||||||
|
from langchain_core.documents import Document
|
||||||
|
|
||||||
|
assert imported_doc is Document
|
Loading…
Reference in New Issue