mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-22 23:52:23 +02:00
auto generate requirements file when missing
This commit is contained in:
@@ -851,47 +851,6 @@ class LocalDeps(NamedTuple):
|
||||
additional_contexts: list[pathlib.Path] = None
|
||||
|
||||
|
||||
def _get_package_metadata_files(package_path: pathlib.Path) -> list[str]:
|
||||
"""Return list of metadata files that exist in package_path.
|
||||
|
||||
These files are needed for dependency resolution and should be copied
|
||||
before source code to optimize Docker layer caching.
|
||||
"""
|
||||
metadata_files = [
|
||||
# Core build files
|
||||
"pyproject.toml",
|
||||
"setup.py",
|
||||
"setup.cfg",
|
||||
"requirements.txt",
|
||||
|
||||
# Referenced in pyproject.toml
|
||||
"README.md",
|
||||
"README.rst",
|
||||
"README.txt",
|
||||
"LICENSE",
|
||||
"LICENSE.txt",
|
||||
"MANIFEST.in",
|
||||
|
||||
# Lock files for reproducible builds
|
||||
"uv.lock",
|
||||
"poetry.lock",
|
||||
"Pipfile.lock",
|
||||
"pdm.lock",
|
||||
|
||||
# Version files
|
||||
"VERSION",
|
||||
"_version.py",
|
||||
"__version__.py",
|
||||
]
|
||||
|
||||
existing_files = []
|
||||
for file in metadata_files:
|
||||
if (package_path / file).exists():
|
||||
existing_files.append(file)
|
||||
|
||||
return existing_files
|
||||
|
||||
|
||||
def _assemble_local_deps(config_path: pathlib.Path, config: Config) -> LocalDeps:
|
||||
config_path = config_path.resolve()
|
||||
# ensure reserved package names are not used
|
||||
@@ -964,6 +923,7 @@ def _assemble_local_deps(config_path: pathlib.Path, config: Config) -> LocalDeps
|
||||
# set working_dir
|
||||
if local_dep == ".":
|
||||
working_dir = f"/deps/{container_name}"
|
||||
requirement_path = f"/deps/{container_name}/requirements.txt"
|
||||
else:
|
||||
# We could not find a pyproject.toml or setup.py, so treat as a faux package
|
||||
if any(file == "__init__.py" for file in files):
|
||||
@@ -995,17 +955,18 @@ def _assemble_local_deps(config_path: pathlib.Path, config: Config) -> LocalDeps
|
||||
faux_pkgs[resolved] = (local_dep, container_path)
|
||||
if local_dep == ".":
|
||||
working_dir = container_path
|
||||
requirement_path = f"{container_path}/requirements.txt"
|
||||
|
||||
# If the faux package has a requirements.txt, we'll add
|
||||
# the path to the list of requirements to install.
|
||||
if "requirements.txt" in files:
|
||||
rfile = resolved / "requirements.txt"
|
||||
pip_reqs.append(
|
||||
(
|
||||
rfile,
|
||||
f"{container_path}/requirements.txt",
|
||||
)
|
||||
# If the package has a requirements.txt, we'll add
|
||||
# the path to the list of requirements to install.
|
||||
if "requirements.txt" in files:
|
||||
rfile = resolved / "requirements.txt"
|
||||
pip_reqs.append(
|
||||
(
|
||||
rfile,
|
||||
requirement_path,
|
||||
)
|
||||
)
|
||||
|
||||
return LocalDeps(pip_reqs, real_pkgs, faux_pkgs, working_dir, additional_contexts)
|
||||
|
||||
@@ -1395,73 +1356,18 @@ RUN set -ex && \\
|
||||
for fullpath, (relpath, destpath) in local_deps.faux_pkgs.items()
|
||||
)
|
||||
|
||||
# Optimized two-stage copy for better Docker layer caching:
|
||||
# Stage 1: Copy only metadata files (rarely change)
|
||||
# Stage 2: Install dependencies (cached until metadata changes)
|
||||
# Stage 3: Copy full source code (changes frequently)
|
||||
# Stage 4: Quick reinstall without dependency resolution (fast)
|
||||
|
||||
local_pkgs_metadata_str_parts = []
|
||||
local_pkgs_install_str_parts = []
|
||||
local_pkgs_source_str_parts = []
|
||||
local_pkgs_reinstall_str_parts = []
|
||||
|
||||
for fullpath, (relpath, name) in local_deps.real_pkgs.items():
|
||||
metadata_files = _get_package_metadata_files(fullpath)
|
||||
|
||||
if fullpath in local_deps.additional_contexts:
|
||||
# For additional contexts, use COPY --from
|
||||
if metadata_files:
|
||||
copy_commands = "\n".join(
|
||||
f"COPY --from={name} {f} /deps/{name}/{f}"
|
||||
for f in metadata_files
|
||||
)
|
||||
local_pkgs_metadata_str_parts.append(
|
||||
f"""# -- Adding package metadata for {name} --
|
||||
RUN mkdir -p /deps/{name}
|
||||
{copy_commands}
|
||||
# -- End of package metadata for {name} --"""
|
||||
)
|
||||
|
||||
local_pkgs_source_str_parts.append(
|
||||
f"""# -- Adding full source for {relpath} --
|
||||
local_pkgs_str = os.linesep.join(
|
||||
(
|
||||
f"""# -- Adding local package {relpath} --
|
||||
COPY --from={name} . /deps/{name}
|
||||
# -- End of full source for {relpath} --"""
|
||||
)
|
||||
else:
|
||||
# For local paths, use ADD
|
||||
if metadata_files:
|
||||
copy_commands = "\n".join(
|
||||
f"ADD {relpath}/{f} /deps/{name}/{f}"
|
||||
for f in metadata_files
|
||||
)
|
||||
local_pkgs_metadata_str_parts.append(
|
||||
f"""# -- Adding package metadata for {name} --
|
||||
RUN mkdir -p /deps/{name}
|
||||
{copy_commands}
|
||||
# -- End of package metadata for {name} --"""
|
||||
)
|
||||
|
||||
local_pkgs_source_str_parts.append(
|
||||
f"""# -- Adding full source for {relpath} --
|
||||
# -- End of local package {relpath} --"""
|
||||
if fullpath in local_deps.additional_contexts
|
||||
else f"""# -- Adding local package {relpath} --
|
||||
ADD {relpath} /deps/{name}
|
||||
# -- End of full source for {relpath} --"""
|
||||
)
|
||||
|
||||
# Install with dependency resolution (cached if metadata unchanged)
|
||||
local_pkgs_install_str_parts.append(
|
||||
f"RUN cd /deps/{name} && {global_reqs_pip_install} ."
|
||||
# -- End of local package {relpath} --"""
|
||||
)
|
||||
|
||||
# Quick reinstall without dependency resolution (fast, after source changes)
|
||||
local_pkgs_reinstall_str_parts.append(
|
||||
f"RUN cd /deps/{name} && {global_reqs_pip_install} --no-deps ."
|
||||
)
|
||||
|
||||
local_pkgs_metadata_str = os.linesep.join(local_pkgs_metadata_str_parts)
|
||||
local_pkgs_install_str = os.linesep.join(local_pkgs_install_str_parts)
|
||||
local_pkgs_source_str = os.linesep.join(local_pkgs_source_str_parts)
|
||||
local_pkgs_reinstall_str = os.linesep.join(local_pkgs_reinstall_str_parts)
|
||||
for fullpath, (relpath, name) in local_deps.real_pkgs.items()
|
||||
)
|
||||
|
||||
install_node_str: str = (
|
||||
"RUN /storage/install-node.sh"
|
||||
@@ -1477,11 +1383,8 @@ ADD {relpath} /deps/{name}
|
||||
pip_config_file_str,
|
||||
pip_pkgs_str,
|
||||
pip_reqs_str,
|
||||
local_pkgs_metadata_str, # Metadata first for caching
|
||||
local_pkgs_str,
|
||||
faux_pkgs_str,
|
||||
local_pkgs_install_str, # Install with deps (cached layer)
|
||||
local_pkgs_source_str, # Source code after dependencies
|
||||
local_pkgs_reinstall_str, # Quick reinstall without deps
|
||||
],
|
||||
)
|
||||
)
|
||||
@@ -1523,14 +1426,21 @@ ADD {relpath} /deps/{name}
|
||||
image_str = docker_tag(config, base_image, api_version)
|
||||
docker_file_contents = [
|
||||
f"FROM {image_str}",
|
||||
"# -- making use of two-stage-cache-optimizations --",
|
||||
"",
|
||||
"# -- making use of requirements.txt optimizations --",
|
||||
os.linesep.join(config["dockerfile_lines"]),
|
||||
"",
|
||||
installs,
|
||||
"",
|
||||
# Note: Individual package installs are now in 'installs' above
|
||||
# This optimizes caching by separating metadata from source code
|
||||
"# -- Installing all local dependencies --",
|
||||
f"""RUN for dep in /deps/*; do \
|
||||
echo "Installing $dep"; \
|
||||
if [ -d "$dep" ]; then \
|
||||
echo "Installing $dep"; \
|
||||
(cd "$dep" && {global_reqs_pip_install} .); \
|
||||
fi; \
|
||||
done""",
|
||||
"# -- End of local dependencies install --",
|
||||
os.linesep.join(env_vars),
|
||||
"",
|
||||
js_inst_str,
|
||||
|
||||
Reference in New Issue
Block a user