mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
7 Commits
fix/slack-
...
uv-migrati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64b1e68d2a | ||
|
|
f2a3a0da56 | ||
|
|
50487f2a9c | ||
|
|
3e6c1f0d27 | ||
|
|
72b200d5a5 | ||
|
|
8968e1f691 | ||
|
|
86374d139d |
3
.github/workflows/e2e-tests.yml
vendored
3
.github/workflows/e2e-tests.yml
vendored
@@ -26,6 +26,9 @@ jobs:
|
||||
with:
|
||||
poetry-version: 2.1.3
|
||||
|
||||
- name: Install UV
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
|
||||
2
.github/workflows/ghcr-build.yml
vendored
2
.github/workflows/ghcr-build.yml
vendored
@@ -116,6 +116,8 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install UV
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- name: Set up Python
|
||||
uses: useblacksmith/setup-python@v6
|
||||
with:
|
||||
|
||||
4
.github/workflows/py-tests.yml
vendored
4
.github/workflows/py-tests.yml
vendored
@@ -42,6 +42,8 @@ jobs:
|
||||
node-version: "22.x"
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install UV
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- name: Set up Python
|
||||
uses: useblacksmith/setup-python@v6
|
||||
with:
|
||||
@@ -81,6 +83,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install UV
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- name: Set up Python
|
||||
uses: useblacksmith/setup-python@v6
|
||||
with:
|
||||
|
||||
2
.github/workflows/pypi-release.yml
vendored
2
.github/workflows/pypi-release.yml
vendored
@@ -32,6 +32,8 @@ jobs:
|
||||
with:
|
||||
virtualenvs-in-project: true
|
||||
virtualenvs-path: ~/.virtualenvs
|
||||
- name: Install UV
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- name: Install Poetry Dependencies
|
||||
run: poetry install --no-interaction --no-root
|
||||
- name: Build poetry project
|
||||
|
||||
74
Makefile
74
Makefile
@@ -14,6 +14,23 @@ PRE_COMMIT_CONFIG_PATH = "./dev_config/python/.pre-commit-config.yaml"
|
||||
PYTHON_VERSION = 3.12
|
||||
KIND_CLUSTER_NAME = "local-hands"
|
||||
|
||||
# Package manager selection: "uv" or "poetry" (default: poetry for backward compatibility)
|
||||
# Set USE_UV=1 to use UV instead of Poetry
|
||||
USE_UV ?= 0
|
||||
ifeq ($(USE_UV),1)
|
||||
PKG_MANAGER = uv
|
||||
PKG_RUN = uv run
|
||||
PKG_INSTALL = uv sync
|
||||
PKG_INSTALL_GROUPS = --group dev --group test --group runtime
|
||||
PKG_INSTALL_ONLY_PREFIX = --only-group
|
||||
else
|
||||
PKG_MANAGER = poetry
|
||||
PKG_RUN = poetry run
|
||||
PKG_INSTALL = poetry install
|
||||
PKG_INSTALL_GROUPS = --with dev,test,runtime
|
||||
PKG_INSTALL_ONLY_PREFIX = --only
|
||||
endif
|
||||
|
||||
# ANSI color codes
|
||||
GREEN=$(shell tput -Txterm setaf 2)
|
||||
YELLOW=$(shell tput -Txterm setaf 3)
|
||||
@@ -40,7 +57,7 @@ check-dependencies:
|
||||
ifeq ($(INSTALL_DOCKER),)
|
||||
@$(MAKE) -s check-docker
|
||||
endif
|
||||
@$(MAKE) -s check-poetry
|
||||
@$(MAKE) -s check-pkg-manager
|
||||
@$(MAKE) -s check-tmux
|
||||
@echo "$(GREEN)Dependencies checked successfully.$(RESET)"
|
||||
|
||||
@@ -116,13 +133,24 @@ check-tmux:
|
||||
echo "$(YELLOW)╚════════════════════════════════════════════════════════════════════════════╝$(RESET)"; \
|
||||
fi
|
||||
|
||||
check-poetry:
|
||||
check-pkg-manager:
|
||||
ifeq ($(USE_UV),1)
|
||||
@echo "$(YELLOW)Checking UV installation...$(RESET)"
|
||||
@if command -v uv > /dev/null; then \
|
||||
echo "$(BLUE)$$(uv --version) is already installed.$(RESET)"; \
|
||||
else \
|
||||
echo "$(RED)UV is not installed. You can install UV by running:"; \
|
||||
echo "$(RED) curl -LsSf https://astral.sh/uv/install.sh | sh$(RESET)"; \
|
||||
echo "$(RED)More detail here: https://docs.astral.sh/uv/getting-started/installation/$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
else
|
||||
@echo "$(YELLOW)Checking Poetry installation...$(RESET)"
|
||||
@if command -v poetry > /dev/null; then \
|
||||
POETRY_VERSION=$(shell poetry --version 2>&1 | sed -E 's/Poetry \(version ([0-9]+\.[0-9]+\.[0-9]+)\)/\1/'); \
|
||||
POETRY_VERSION=$$(poetry --version 2>&1 | sed -E 's/Poetry \(version ([0-9]+\.[0-9]+\.[0-9]+)\)/\1/'); \
|
||||
IFS='.' read -r -a POETRY_VERSION_ARRAY <<< "$$POETRY_VERSION"; \
|
||||
if [ $${POETRY_VERSION_ARRAY[0]} -gt 1 ] || ([ $${POETRY_VERSION_ARRAY[0]} -eq 1 ] && [ $${POETRY_VERSION_ARRAY[1]} -ge 8 ]); then \
|
||||
echo "$(BLUE)$(shell poetry --version) is already installed.$(RESET)"; \
|
||||
echo "$(BLUE)$$(poetry --version) is already installed.$(RESET)"; \
|
||||
else \
|
||||
echo "$(RED)Poetry 1.8 or later is required. You can install poetry by running the following command, then adding Poetry to your PATH:"; \
|
||||
echo "$(RED) curl -sSL https://install.python-poetry.org | python$(PYTHON_VERSION) -$(RESET)"; \
|
||||
@@ -135,6 +163,10 @@ check-poetry:
|
||||
echo "$(RED)More detail here: https://python-poetry.org/docs/#installing-with-the-official-installer$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
endif
|
||||
|
||||
# Legacy alias for backward compatibility
|
||||
check-poetry: check-pkg-manager
|
||||
|
||||
install-python-dependencies:
|
||||
@echo "$(GREEN)Installing Python dependencies...$(RESET)"
|
||||
@@ -142,6 +174,21 @@ install-python-dependencies:
|
||||
echo "Defaulting TZ (timezone) to UTC"; \
|
||||
export TZ="UTC"; \
|
||||
fi
|
||||
ifeq ($(USE_UV),1)
|
||||
@echo "$(BLUE)Using UV for dependency management$(RESET)"
|
||||
@if [ "$(shell uname)" = "Darwin" ]; then \
|
||||
echo "$(BLUE)Installing chroma-hnswlib...$(RESET)"; \
|
||||
export HNSWLIB_NO_NATIVE=1; \
|
||||
uv pip install chroma-hnswlib; \
|
||||
fi
|
||||
@if [ -n "${DEP_GROUP}" ]; then \
|
||||
echo "Installing only DEP_GROUP=${DEP_GROUP}"; \
|
||||
uv sync --only-group $${DEP_GROUP}; \
|
||||
else \
|
||||
uv sync --group dev --group test --group runtime; \
|
||||
fi
|
||||
else
|
||||
@echo "$(BLUE)Using Poetry for dependency management$(RESET)"
|
||||
poetry env use python$(PYTHON_VERSION)
|
||||
@if [ "$(shell uname)" = "Darwin" ]; then \
|
||||
echo "$(BLUE)Installing chroma-hnswlib...$(RESET)"; \
|
||||
@@ -154,15 +201,16 @@ install-python-dependencies:
|
||||
else \
|
||||
poetry install --with dev,test,runtime; \
|
||||
fi
|
||||
endif
|
||||
@if [ "${INSTALL_PLAYWRIGHT}" != "false" ] && [ "${INSTALL_PLAYWRIGHT}" != "0" ]; then \
|
||||
if [ -f "/etc/manjaro-release" ]; then \
|
||||
echo "$(BLUE)Detected Manjaro Linux. Installing Playwright dependencies...$(RESET)"; \
|
||||
poetry run pip install playwright; \
|
||||
poetry run playwright install chromium; \
|
||||
$(PKG_RUN) pip install playwright; \
|
||||
$(PKG_RUN) playwright install chromium; \
|
||||
else \
|
||||
if [ ! -f cache/playwright_chromium_is_installed.txt ]; then \
|
||||
echo "Running playwright install --with-deps chromium..."; \
|
||||
poetry run playwright install --with-deps chromium; \
|
||||
$(PKG_RUN) playwright install --with-deps chromium; \
|
||||
mkdir -p cache; \
|
||||
touch cache/playwright_chromium_is_installed.txt; \
|
||||
else \
|
||||
@@ -182,15 +230,15 @@ install-frontend-dependencies: check-npm check-nodejs
|
||||
@cd frontend && npm install
|
||||
@echo "$(GREEN)Frontend dependencies installed successfully.$(RESET)"
|
||||
|
||||
install-pre-commit-hooks: check-python check-poetry install-python-dependencies
|
||||
install-pre-commit-hooks: check-python check-pkg-manager install-python-dependencies
|
||||
@echo "$(YELLOW)Installing pre-commit hooks...$(RESET)"
|
||||
@git config --unset-all core.hooksPath || true
|
||||
@poetry run pre-commit install --config $(PRE_COMMIT_CONFIG_PATH)
|
||||
@$(PKG_RUN) pre-commit install --config $(PRE_COMMIT_CONFIG_PATH)
|
||||
@echo "$(GREEN)Pre-commit hooks installed successfully.$(RESET)"
|
||||
|
||||
lint-backend: install-pre-commit-hooks
|
||||
@echo "$(YELLOW)Running linters...$(RESET)"
|
||||
@poetry run pre-commit run --all-files --show-diff-on-failure --config $(PRE_COMMIT_CONFIG_PATH)
|
||||
@$(PKG_RUN) pre-commit run --all-files --show-diff-on-failure --config $(PRE_COMMIT_CONFIG_PATH)
|
||||
|
||||
lint-frontend: install-frontend-dependencies
|
||||
@echo "$(YELLOW)Running linters for frontend...$(RESET)"
|
||||
@@ -248,7 +296,7 @@ build-frontend:
|
||||
# Start backend
|
||||
start-backend:
|
||||
@echo "$(YELLOW)Starting backend...$(RESET)"
|
||||
@poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "./workspace"
|
||||
@$(PKG_RUN) uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "./workspace"
|
||||
|
||||
# Start frontend
|
||||
start-frontend:
|
||||
@@ -270,7 +318,7 @@ _run_setup:
|
||||
fi
|
||||
@mkdir -p logs
|
||||
@echo "$(YELLOW)Starting backend server...$(RESET)"
|
||||
@poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) &
|
||||
@$(PKG_RUN) uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) &
|
||||
@echo "$(YELLOW)Waiting for the backend to start...$(RESET)"
|
||||
@until nc -z localhost $(BACKEND_PORT); do sleep 0.1; done
|
||||
@echo "$(GREEN)Backend started successfully.$(RESET)"
|
||||
@@ -367,5 +415,5 @@ help:
|
||||
@echo " $(GREEN)help$(RESET) - Display this help message, providing information on available targets."
|
||||
|
||||
# Phony targets
|
||||
.PHONY: build check-dependencies check-system check-python check-npm check-nodejs check-docker check-poetry install-python-dependencies install-frontend-dependencies install-pre-commit-hooks lint-backend lint-frontend lint test-frontend test build-frontend start-backend start-frontend _run_setup run run-wsl setup-config setup-config-prompts setup-config-basic openhands-cloud-run docker-dev docker-run clean help
|
||||
.PHONY: build check-dependencies check-system check-python check-npm check-nodejs check-docker check-pkg-manager check-poetry install-python-dependencies install-frontend-dependencies install-pre-commit-hooks lint-backend lint-frontend lint test-frontend test build-frontend start-backend start-frontend _run_setup run run-wsl setup-config setup-config-prompts setup-config-basic openhands-cloud-run docker-dev docker-run clean help
|
||||
.PHONY: kind
|
||||
|
||||
@@ -15,6 +15,9 @@ FROM base AS backend-builder
|
||||
WORKDIR /app
|
||||
ENV PYTHONPATH='/app'
|
||||
|
||||
# Package manager selection: set USE_UV=1 to use UV instead of Poetry
|
||||
ARG USE_UV=0
|
||||
|
||||
ENV POETRY_NO_INTERACTION=1 \
|
||||
POETRY_VIRTUALENVS_IN_PROJECT=1 \
|
||||
POETRY_VIRTUALENVS_CREATE=1 \
|
||||
@@ -22,11 +25,21 @@ ENV POETRY_NO_INTERACTION=1 \
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y curl make git build-essential jq gettext \
|
||||
&& python3 -m pip install poetry --break-system-packages
|
||||
&& python3 -m pip install poetry --break-system-packages \
|
||||
&& curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
COPY pyproject.toml poetry.lock ./
|
||||
# Copy both lock files for flexibility
|
||||
COPY pyproject.toml poetry.lock uv.lock ./
|
||||
RUN touch README.md
|
||||
RUN export POETRY_CACHE_DIR && poetry install --no-root && rm -rf $POETRY_CACHE_DIR
|
||||
|
||||
# Install dependencies using selected package manager
|
||||
RUN if [ "$USE_UV" = "1" ]; then \
|
||||
echo "Installing dependencies with UV..." && \
|
||||
/root/.local/bin/uv sync --no-dev; \
|
||||
else \
|
||||
echo "Installing dependencies with Poetry..." && \
|
||||
export POETRY_CACHE_DIR && poetry install --no-root && rm -rf $POETRY_CACHE_DIR; \
|
||||
fi
|
||||
|
||||
FROM base AS openhands-app
|
||||
|
||||
@@ -76,7 +89,7 @@ COPY --chown=openhands:openhands --chmod=770 --from=backend-builder ${VIRTUAL_EN
|
||||
COPY --chown=openhands:openhands --chmod=770 ./skills ./skills
|
||||
COPY --chown=openhands:openhands --chmod=770 ./openhands ./openhands
|
||||
COPY --chown=openhands:openhands --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins
|
||||
COPY --chown=openhands:openhands pyproject.toml poetry.lock README.md MANIFEST.in LICENSE ./
|
||||
COPY --chown=openhands:openhands pyproject.toml poetry.lock uv.lock README.md MANIFEST.in LICENSE ./
|
||||
|
||||
# This is run as "openhands" user, and will create __pycache__ with openhands:openhands ownership
|
||||
RUN python openhands/core/download.py # No-op to download assets
|
||||
|
||||
@@ -69,6 +69,10 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||
RUN curl -fsSL https://install.python-poetry.org | python3.12 - \
|
||||
&& ln -s ~/.local/bin/poetry /usr/local/bin/poetry
|
||||
|
||||
# UV (alternative package manager)
|
||||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
|
||||
&& ln -s ~/.local/bin/uv /usr/local/bin/uv
|
||||
|
||||
#
|
||||
RUN <<EOF
|
||||
#!/bin/bash
|
||||
@@ -80,9 +84,10 @@ gh --version | head -n 1
|
||||
git --version
|
||||
#
|
||||
python --version
|
||||
echo node `node --version`
|
||||
echo npm `npm --version`
|
||||
echo node \`node --version\`
|
||||
echo npm \`npm --version\`
|
||||
poetry --version
|
||||
uv --version
|
||||
netcat -h 2>&1 | head -n 1
|
||||
" > /version.sh
|
||||
chmod a+x /version.sh
|
||||
|
||||
@@ -289,12 +289,13 @@ def prep_build_folder(
|
||||
# Copy the 'skills' directory (Skills)
|
||||
shutil.copytree(Path(project_root, 'skills'), Path(build_folder, 'code', 'skills'))
|
||||
|
||||
# Copy pyproject.toml and poetry.lock files
|
||||
for file in ['pyproject.toml', 'poetry.lock']:
|
||||
# Copy pyproject.toml and lock files (poetry.lock and uv.lock if it exists)
|
||||
for file in ['pyproject.toml', 'poetry.lock', 'uv.lock']:
|
||||
src = Path(openhands_source_dir, file)
|
||||
if not src.exists():
|
||||
src = Path(project_root, file)
|
||||
shutil.copy2(src, Path(build_folder, 'code', file))
|
||||
if src.exists():
|
||||
shutil.copy2(src, Path(build_folder, 'code', file))
|
||||
|
||||
# Create a Dockerfile and write it to build_folder
|
||||
dockerfile_content = _generate_dockerfile(
|
||||
@@ -328,13 +329,15 @@ def get_hash_for_lock_files(base_image: str, enable_browser: bool = True) -> str
|
||||
# Only include enable_browser in hash when it's False for backward compatibility
|
||||
if not enable_browser:
|
||||
md5.update(str(enable_browser).encode())
|
||||
for file in ['pyproject.toml', 'poetry.lock']:
|
||||
# Include pyproject.toml and lock files (poetry.lock and uv.lock if it exists)
|
||||
for file in ['pyproject.toml', 'poetry.lock', 'uv.lock']:
|
||||
src = Path(openhands_source_dir, file)
|
||||
if not src.exists():
|
||||
src = Path(openhands_source_dir.parent, file)
|
||||
with open(src, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b''):
|
||||
md5.update(chunk)
|
||||
if src.exists():
|
||||
with open(src, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b''):
|
||||
md5.update(chunk)
|
||||
# We get away with truncation because we want something that is unique
|
||||
# rather than something that is cryptographically secure
|
||||
result = truncate_hash(md5.hexdigest())
|
||||
|
||||
@@ -296,7 +296,7 @@ RUN /openhands/micromamba/bin/micromamba create -n openhands -y && \
|
||||
/openhands/micromamba/bin/micromamba install -n openhands -c conda-forge poetry python=3.12 -y
|
||||
USER root
|
||||
|
||||
# Create a clean openhands directory including only the pyproject.toml, poetry.lock and openhands/__init__.py
|
||||
# Create a clean openhands directory including only the pyproject.toml, poetry.lock, uv.lock and openhands/__init__.py
|
||||
RUN \
|
||||
if [ -d /openhands/code ]; then rm -rf /openhands/code; fi && \
|
||||
mkdir -p /openhands/code/openhands && \
|
||||
@@ -307,6 +307,8 @@ RUN \
|
||||
git config --global user.email "openhands@all-hands.dev"
|
||||
|
||||
COPY --chown=openhands:openhands ./code/pyproject.toml ./code/poetry.lock /openhands/code/
|
||||
# Copy uv.lock if it exists (for future UV support)
|
||||
COPY --chown=openhands:openhands ./code/uv.lock* /openhands/code/
|
||||
|
||||
{{ install_dependencies_user() }}
|
||||
{{ install_dependencies_root() }}
|
||||
@@ -342,6 +344,8 @@ RUN \
|
||||
# ================================================================
|
||||
RUN if [ -d /openhands/code/openhands ]; then rm -rf /openhands/code/openhands; fi
|
||||
COPY --chown=openhands:openhands ./code/pyproject.toml ./code/poetry.lock /openhands/code/
|
||||
# Copy uv.lock if it exists (for future UV support)
|
||||
COPY --chown=openhands:openhands ./code/uv.lock* /openhands/code/
|
||||
RUN if [ -d /openhands/code/skills ]; then rm -rf /openhands/code/skills; fi
|
||||
COPY --chown=openhands:openhands ./code/skills /openhands/code/skills
|
||||
COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands
|
||||
|
||||
4
poetry.lock
generated
4
poetry.lock
generated
@@ -1816,7 +1816,7 @@ files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
markers = {main = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\"", dev = "os_name == \"nt\" or sys_platform == \"win32\"", runtime = "sys_platform == \"win32\"", test = "sys_platform == \"win32\""}
|
||||
markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\"", dev = "os_name == \"nt\" or sys_platform == \"win32\"", runtime = "sys_platform == \"win32\"", test = "sys_platform == \"win32\""}
|
||||
|
||||
[[package]]
|
||||
name = "comm"
|
||||
@@ -16846,4 +16846,4 @@ third-party-runtimes = ["daytona", "e2b-code-interpreter", "modal", "runloop-api
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.12,<3.14"
|
||||
content-hash = "78c01d3e121d5f27a4120d043408ac0aa93fa95fe0c2c7d678161b08ccb582c1"
|
||||
content-hash = "ef3a6a2526eec15650284a245d0bd0dbf764514401799fc15677d72a7a09b2de"
|
||||
|
||||
175
pyproject.toml
175
pyproject.toml
@@ -1,8 +1,174 @@
|
||||
[build-system]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = [
|
||||
"poetry-core",
|
||||
requires = [ "poetry-core" ]
|
||||
|
||||
[project]
|
||||
name = "openhands-ai"
|
||||
version = "1.1.0"
|
||||
description = "OpenHands: Code Less, Make More"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
authors = [ { name = "OpenHands", email = "contact@all-hands.dev" } ]
|
||||
requires-python = ">=3.12,<3.14"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
# Main dependencies (mirrors [tool.poetry.dependencies] for UV compatibility)
|
||||
dependencies = [
|
||||
"aiohttp>=3.9,!=3.11.13",
|
||||
"anthropic[vertex]",
|
||||
"anyio==4.9",
|
||||
"asyncpg>=0.30",
|
||||
"bashlex>=0.18",
|
||||
"boto3",
|
||||
"browsergym-core==0.13.3",
|
||||
"deprecated",
|
||||
"deprecation>=2.1",
|
||||
"dirhash",
|
||||
"docker",
|
||||
"fastapi",
|
||||
"fastmcp>=2.12.4",
|
||||
"google-api-python-client>=2.164",
|
||||
"google-auth-httplib2",
|
||||
"google-auth-oauthlib",
|
||||
"google-cloud-aiplatform",
|
||||
"google-genai",
|
||||
"html2text",
|
||||
"httpx-aiohttp>=0.1.8",
|
||||
"ipywidgets>=8.1.5",
|
||||
"jinja2>=3.1.6",
|
||||
"joblib",
|
||||
"json-repair",
|
||||
"jupyter-kernel-gateway",
|
||||
"kubernetes>=33.1",
|
||||
"libtmux>=0.46.2",
|
||||
"litellm!=1.64.4,!=1.67.*,>=1.74.3",
|
||||
"lmnr>=0.7.20",
|
||||
"memory-profiler>=0.61",
|
||||
"numpy",
|
||||
"openai==2.8",
|
||||
"openhands-aci==0.3.2",
|
||||
"openhands-agent-server==1.8.1",
|
||||
"openhands-sdk==1.8.1",
|
||||
"openhands-tools==1.8.1",
|
||||
"opentelemetry-api>=1.33.1",
|
||||
"opentelemetry-exporter-otlp-proto-grpc>=1.33.1",
|
||||
"pathspec>=0.12.1",
|
||||
"pexpect",
|
||||
"pg8000>=1.31.5",
|
||||
"pillow>=11.3",
|
||||
"playwright>=1.55",
|
||||
"poetry>=2.1.2",
|
||||
"prompt-toolkit>=3.0.50",
|
||||
"protobuf>=5,<6",
|
||||
"psutil",
|
||||
"pybase62>=1",
|
||||
"pygithub>=2.5",
|
||||
"pyjwt>=2.9",
|
||||
"pylatexenc",
|
||||
"pypdf>=6",
|
||||
"python-docx",
|
||||
"python-dotenv",
|
||||
"python-frontmatter>=1.1",
|
||||
"python-jose[cryptography]>=3.3",
|
||||
"python-json-logger>=3.2.1",
|
||||
"python-multipart",
|
||||
"python-pptx",
|
||||
"python-socketio>=5.11.4",
|
||||
"pythonnet",
|
||||
"pyyaml>=6.0.2",
|
||||
"qtconsole>=5.6.1",
|
||||
"rapidfuzz>=3.9",
|
||||
"redis>=5.2,<7",
|
||||
"requests>=2.32.5",
|
||||
"setuptools>=78.1.1",
|
||||
"shellingham>=1.5.4",
|
||||
"sqlalchemy[asyncio]>=2.0.40",
|
||||
"sse-starlette>=3.0.2",
|
||||
"starlette>=0.48",
|
||||
"tenacity>=8.5,<10",
|
||||
"termcolor",
|
||||
"toml",
|
||||
"tornado>=6.5",
|
||||
"types-toml",
|
||||
"urllib3>=2.6.3",
|
||||
"uvicorn",
|
||||
"whatthepatch>=1.0.6",
|
||||
"zope-interface==7.2",
|
||||
]
|
||||
|
||||
optional-dependencies.third_party_runtimes = [
|
||||
"daytona==0.24.2",
|
||||
"e2b-code-interpreter>=2",
|
||||
"modal>=0.66.26,<1.2",
|
||||
"runloop-api-client==0.50",
|
||||
]
|
||||
urls.Homepage = "https://github.com/OpenHands/OpenHands"
|
||||
urls.Repository = "https://github.com/OpenHands/OpenHands"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"build",
|
||||
"mypy==1.17",
|
||||
"pre-commit==4.2",
|
||||
"pytest>=8.4",
|
||||
"pytest-asyncio>=1.3",
|
||||
"ruff==0.12.5",
|
||||
"types-setuptools",
|
||||
]
|
||||
test = [
|
||||
"gevent>=24.2.1,<26",
|
||||
"pandas",
|
||||
"pytest",
|
||||
"pytest-asyncio",
|
||||
"pytest-cov",
|
||||
"pytest-forked",
|
||||
"pytest-playwright>=0.7",
|
||||
"pytest-timeout>=2.4",
|
||||
"pytest-xdist",
|
||||
"reportlab",
|
||||
]
|
||||
runtime = [
|
||||
"flake8",
|
||||
"jupyterlab",
|
||||
"notebook",
|
||||
]
|
||||
evaluation = [
|
||||
"boto3-stubs[s3]>=1.37.19",
|
||||
"browsergym==0.13.3",
|
||||
"browsergym-miniwob==0.13.3",
|
||||
"browsergym-visualwebarena==0.13.3",
|
||||
"browsergym-webarena==0.13.3",
|
||||
"commit0",
|
||||
"datasets",
|
||||
"evaluate",
|
||||
"func-timeout",
|
||||
"gdown",
|
||||
"joblib",
|
||||
"matplotlib",
|
||||
"multi-swe-bench==0.1.2",
|
||||
"pandas",
|
||||
"pyarrow==21",
|
||||
"retry",
|
||||
"seaborn",
|
||||
"streamlit",
|
||||
"swebench",
|
||||
"swegym",
|
||||
"sympy",
|
||||
"tabulate",
|
||||
"visualswebench",
|
||||
"whatthepatch",
|
||||
]
|
||||
testgeneval = [
|
||||
"fuzzywuzzy>=0.18",
|
||||
"python-levenshtein>=0.26.1,<0.28",
|
||||
"rouge>=1.0.1",
|
||||
"tree-sitter-python>=0.23.6",
|
||||
]
|
||||
|
||||
# UV source configuration for git dependencies in evaluation group
|
||||
|
||||
[tool.poetry]
|
||||
name = "openhands-ai"
|
||||
@@ -221,3 +387,8 @@ lint.pydocstyle.convention = "google"
|
||||
concurrency = [ "gevent" ]
|
||||
relative_files = true
|
||||
omit = [ "enterprise/tests/*", "**/test_*" ]
|
||||
|
||||
[tool.uv.sources]
|
||||
visualswebench = { git = "https://github.com/luolin101/Visual-SWE-bench.git" }
|
||||
swegym = { git = "https://github.com/SWE-Gym/SWE-Bench-Package.git" }
|
||||
swebench = { git = "https://github.com/ryanhoangt/SWE-bench.git", rev = "fix-modal-patch-eval" }
|
||||
|
||||
@@ -62,12 +62,15 @@ def _check_source_code_in_dir(temp_dir):
|
||||
assert os.path.exists(os.path.join(code_dir, 'pyproject.toml'))
|
||||
|
||||
# The source code should only include the `openhands` folder,
|
||||
# and pyproject.toml & poetry.lock that are needed to build the runtime image
|
||||
assert set(os.listdir(code_dir)) == {
|
||||
'openhands',
|
||||
'pyproject.toml',
|
||||
'poetry.lock',
|
||||
}
|
||||
# and pyproject.toml & lock files that are needed to build the runtime image
|
||||
expected_files = {'openhands', 'pyproject.toml', 'poetry.lock'}
|
||||
# uv.lock is optional - include it if it exists
|
||||
if os.path.exists(os.path.join(code_dir, 'uv.lock')):
|
||||
expected_files.add('uv.lock')
|
||||
# skills directory may also be present
|
||||
if os.path.exists(os.path.join(code_dir, 'skills')):
|
||||
expected_files.add('skills')
|
||||
assert set(os.listdir(code_dir)) == expected_files
|
||||
assert os.path.exists(os.path.join(code_dir, 'openhands'))
|
||||
assert os.path.isdir(os.path.join(code_dir, 'openhands'))
|
||||
|
||||
@@ -89,9 +92,11 @@ def test_prep_build_folder(temp_dir):
|
||||
extra_deps=None,
|
||||
)
|
||||
|
||||
# make sure that the code (openhands/) and microagents folder were copied
|
||||
# make sure that the code (openhands/) and skills folder were copied
|
||||
assert shutil_mock.copytree.call_count == 2
|
||||
assert shutil_mock.copy2.call_count == 2
|
||||
# copy2 is called for pyproject.toml, poetry.lock, and optionally uv.lock
|
||||
# The exact count depends on whether uv.lock exists
|
||||
assert shutil_mock.copy2.call_count >= 2
|
||||
|
||||
# Now check dockerfile is in the folder
|
||||
dockerfile_path = os.path.join(temp_dir, 'Dockerfile')
|
||||
@@ -100,26 +105,35 @@ def test_prep_build_folder(temp_dir):
|
||||
|
||||
|
||||
def test_get_hash_for_lock_files():
|
||||
with patch('builtins.open', mock_open(read_data='mock-data'.encode())):
|
||||
# Mock Path.exists to return True for all files including uv.lock
|
||||
with (
|
||||
patch('builtins.open', mock_open(read_data='mock-data'.encode())),
|
||||
patch.object(Path, 'exists', return_value=True),
|
||||
):
|
||||
hash = get_hash_for_lock_files('some_base_image', enable_browser=True)
|
||||
# Since we mocked open to always return "mock_data", the hash is the result
|
||||
# of hashing the name of the base image followed by "mock-data" twice
|
||||
# of hashing the name of the base image followed by "mock-data" three times
|
||||
# (pyproject.toml, poetry.lock, uv.lock)
|
||||
md5 = hashlib.md5()
|
||||
md5.update('some_base_image'.encode())
|
||||
for _ in range(2):
|
||||
for _ in range(3): # pyproject.toml, poetry.lock, uv.lock
|
||||
md5.update('mock-data'.encode())
|
||||
assert hash == truncate_hash(md5.hexdigest())
|
||||
|
||||
|
||||
def test_get_hash_for_lock_files_different_enable_browser():
|
||||
with patch('builtins.open', mock_open(read_data='mock-data'.encode())):
|
||||
# Mock Path.exists to return True for all files including uv.lock
|
||||
with (
|
||||
patch('builtins.open', mock_open(read_data='mock-data'.encode())),
|
||||
patch.object(Path, 'exists', return_value=True),
|
||||
):
|
||||
hash_true = get_hash_for_lock_files('some_base_image', enable_browser=True)
|
||||
hash_false = get_hash_for_lock_files('some_base_image', enable_browser=False)
|
||||
|
||||
# Hash with enable_browser=True should not include the enable_browser value
|
||||
md5_true = hashlib.md5()
|
||||
md5_true.update('some_base_image'.encode())
|
||||
for _ in range(2):
|
||||
for _ in range(3): # pyproject.toml, poetry.lock, uv.lock
|
||||
md5_true.update('mock-data'.encode())
|
||||
expected_hash_true = truncate_hash(md5_true.hexdigest())
|
||||
|
||||
@@ -127,7 +141,7 @@ def test_get_hash_for_lock_files_different_enable_browser():
|
||||
md5_false = hashlib.md5()
|
||||
md5_false.update('some_base_image'.encode())
|
||||
md5_false.update('False'.encode()) # enable_browser=False is included
|
||||
for _ in range(2):
|
||||
for _ in range(3): # pyproject.toml, poetry.lock, uv.lock
|
||||
md5_false.update('mock-data'.encode())
|
||||
expected_hash_false = truncate_hash(md5_false.hexdigest())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user