From 7888dc505e13e2ac831a4f0dc3318486f70bb24d Mon Sep 17 00:00:00 2001 From: Sterling Dreyer Date: Fri, 1 Aug 2025 12:12:35 -0700 Subject: [PATCH] Fix venv files not being found (#525) --- libs/arcade-cli/arcade_cli/deployment.py | 4 +- libs/arcade-core/arcade_core/toolkit.py | 43 ++++++++------ libs/arcade-core/pyproject.toml | 2 +- libs/tests/core/test_toolkit.py | 72 ++---------------------- pyproject.toml | 4 +- 5 files changed, 34 insertions(+), 91 deletions(-) diff --git a/libs/arcade-cli/arcade_cli/deployment.py b/libs/arcade-cli/arcade_cli/deployment.py index 41b44c80..47ff2e2c 100644 --- a/libs/arcade-cli/arcade_cli/deployment.py +++ b/libs/arcade-cli/arcade_cli/deployment.py @@ -10,7 +10,7 @@ from typing import Any import toml from arcade_core import Toolkit -from arcade_core.toolkit import valid_path +from arcade_core.toolkit import Validate from arcadepy import Arcade, NotFoundError from httpx import Client, ConnectError, HTTPStatusError, TimeoutException from packaging.requirements import Requirement @@ -230,7 +230,7 @@ class Worker(BaseModel): def exclude_filter(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None: """Filter for files/directories to exclude from the compressed package""" - if not valid_path(tarinfo.name): + if not Validate.path(tarinfo.name): return None return tarinfo diff --git a/libs/arcade-core/arcade_core/toolkit.py b/libs/arcade-core/arcade_core/toolkit.py index 2325219a..0b780410 100644 --- a/libs/arcade-core/arcade_core/toolkit.py +++ b/libs/arcade-core/arcade_core/toolkit.py @@ -221,7 +221,7 @@ class Toolkit(BaseModel): """ # Get all python files in the package directory try: - modules = [f for f in package_dir.glob("**/*.py") if f.is_file() and valid_path(f)] + modules = [f for f in package_dir.glob("**/*.py") if f.is_file() and Validate.path(f)] except OSError as e: raise ToolkitLoadError( f"Failed to locate Python files in package directory for '{package_name}'." @@ -289,23 +289,30 @@ def get_package_directory(package_name: str) -> str: raise ImportError(f"Package {package_name} does not have a file path associated with it") -def valid_path(path: str | Path) -> bool: - """ - Validate if a path is valid to be served or deployed. - """ - # Check both POSIX and Windows interpretations - posix_path = PurePosixPath(path) - windows_path = PureWindowsPath(path) +class Validate: + warn = True - # Get all possible parts from both interpretations - all_parts = set(posix_path.parts) | set(windows_path.parts) + @classmethod + def path(cls, path: str | Path) -> bool: + """ + Validate if a path is valid to be served or deployed. + """ + # Check both POSIX and Windows interpretations + posix_path = PurePosixPath(path) + windows_path = PureWindowsPath(path) - for part in all_parts: - if part.startswith("."): - return False - if part in {"dist", "build", "__pycache__", "venv", "coverage.xml"}: - return False - if part.endswith(".lock"): - return False + # Get all possible parts from both interpretations + all_parts = set(posix_path.parts) | set(windows_path.parts) - return True + for part in all_parts: + if (part == "venv" or part.startswith(".")) and cls.warn: + print( + f"⚠️ Your package may contain a venv directory or hidden files. We suggest moving these out of the toolkit directory to avoid deployment issues: {path}" + ) + cls.warn = False + if part in {"dist", "build", "__pycache__", "coverage.xml"}: + return False + if part.endswith(".lock"): + return False + + return True diff --git a/libs/arcade-core/pyproject.toml b/libs/arcade-core/pyproject.toml index d1cb9d5d..2c2f0126 100644 --- a/libs/arcade-core/pyproject.toml +++ b/libs/arcade-core/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "arcade-core" -version = "2.2.1" +version = "2.2.2" description = "Arcade Core - Core library for Arcade platform" readme = "README.md" license = {text = "MIT"} diff --git a/libs/tests/core/test_toolkit.py b/libs/tests/core/test_toolkit.py index 91e0c70a..8cea12c5 100644 --- a/libs/tests/core/test_toolkit.py +++ b/libs/tests/core/test_toolkit.py @@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch import pytest from arcade_core.errors import ToolkitLoadError -from arcade_core.toolkit import Toolkit, valid_path +from arcade_core.toolkit import Toolkit, Validate class TestToolkit: @@ -400,7 +400,7 @@ class TestValidateFile: class TestValidPath: - """Test the valid_path function for path validation during deployment and serving.""" + """Test the Validate.path function for path validation during deployment and serving.""" @pytest.mark.parametrize( "path_input", @@ -452,80 +452,25 @@ class TestValidPath: Path("file.py"), Path("src/module.py"), ], - ids=[ - "simple_file_py", - "simple_module_py", - "simple_utils_py", - "simple_main_py", - "simple_readme_md", - "simple_config_json", - "nested_src_main", - "nested_lib_utils", - "nested_package_subpackage", - "nested_tools_scripts", - "nested_docs_api", - "nested_tests_unit", - "deep_nested_very_deep", - "deep_nested_a_b_c", - "edge_case_empty_string", - "edge_case_single_a", - "edge_case_single_1", - "contains_dist_but_valid", - "contains_build_but_valid", - "contains_lock_but_valid", - "contains_unlock_valid", - "case_sensitive_DIST", - "case_sensitive_Build", - "case_sensitive_VENV", - "case_sensitive_LOCK", - "windows_src_module", - "windows_lib_utils", - "absolute_home_user", - "unicode_cyrillic", - "unicode_chinese", - "unicode_accented", - "spaces_in_filename", - "dashes_in_filename", - "underscores_in_filename", - "path_object_file", - "path_object_nested", - ], ) def test_valid_paths(self, path_input): """Test that valid paths are accepted.""" - assert valid_path(path_input) is True + assert Validate.path(path_input) is True @pytest.mark.parametrize( "path_input", [ - # Hidden files (starting with .) - ".hidden", - ".gitignore", - ".env", - ".DS_Store", - ".vscode", - ".pytest_cache", - # Paths containing hidden directories - ".hidden/file.py", - "src/.cache/data.py", - "lib/.git/config", - ".vscode/settings.json", - "deep/.hidden/nested/file.py", - "normal/.DS_Store", # Excluded directories (exact matches) "dist", "build", "__pycache__", - "venv", "coverage.xml", # Paths containing excluded directories "dist/bundle.js", "build/output/file.py", "src/__pycache__/module.cpython-39.pyc", - "venv/lib/python3.9/site-packages/module.py", "project/build/artifacts/file.py", "lib/dist/package/module.py", - "tools/venv/bin/python", # Lock files (ending with .lock) "package.lock", "poetry.lock", @@ -539,28 +484,19 @@ class TestValidPath: "frontend/yarn.lock", "backend/poetry.lock", "deep/nested/path/file.lock", - # Multiple violations - ".hidden/dist/file.py", # hidden + excluded dir - "build/.cache/file.py", # excluded dir + hidden - "venv/package.lock", # excluded dir + lock file - ".git/hooks/file.lock", # hidden + lock file # Windows-style paths that should be invalid "dist\\bundle.js", - ".hidden\\file.py", "src\\package.lock", # Absolute paths that should be invalid - "/home/user/project/.hidden/file.py", "/home/user/project/dist/file.py", "/opt/project/package.lock", # Unicode with exclusion patterns - ".файл", # Hidden Cyrillic file "文件.lock", # Chinese lock file # Path objects - Path(".hidden/file.py"), Path("dist/bundle.js"), Path("package.lock"), ], ) def test_invalid_paths(self, path_input): """Test that invalid paths are rejected.""" - assert valid_path(path_input) is False + assert Validate.path(path_input) is False diff --git a/pyproject.toml b/pyproject.toml index 30035efc..7f15cfaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "arcade-ai" -version = "2.1.3" +version = "2.1.4" description = "Arcade.dev - Tool Calling platform for Agents" readme = "README.md" license = {file = "LICENSE"} @@ -21,7 +21,7 @@ requires-python = ">=3.10" dependencies = [ # CLI dependencies - "arcade-core>=2.2.1,<3.0.0", + "arcade-core>=2.2.2,<3.0.0", "typer==0.10.0", "rich==13.9.4", "Jinja2==3.1.6",