Fix venv files not being found (#525)

This commit is contained in:
Sterling Dreyer 2025-08-01 12:12:35 -07:00 committed by GitHub
parent 21f892c0a0
commit 7888dc505e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 34 additions and 91 deletions

View file

@ -10,7 +10,7 @@ from typing import Any
import toml import toml
from arcade_core import Toolkit from arcade_core import Toolkit
from arcade_core.toolkit import valid_path from arcade_core.toolkit import Validate
from arcadepy import Arcade, NotFoundError from arcadepy import Arcade, NotFoundError
from httpx import Client, ConnectError, HTTPStatusError, TimeoutException from httpx import Client, ConnectError, HTTPStatusError, TimeoutException
from packaging.requirements import Requirement from packaging.requirements import Requirement
@ -230,7 +230,7 @@ class Worker(BaseModel):
def exclude_filter(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None: def exclude_filter(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None:
"""Filter for files/directories to exclude from the compressed package""" """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 None
return tarinfo return tarinfo

View file

@ -221,7 +221,7 @@ class Toolkit(BaseModel):
""" """
# Get all python files in the package directory # Get all python files in the package directory
try: 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: except OSError as e:
raise ToolkitLoadError( raise ToolkitLoadError(
f"Failed to locate Python files in package directory for '{package_name}'." 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") raise ImportError(f"Package {package_name} does not have a file path associated with it")
def valid_path(path: str | Path) -> bool: class Validate:
""" warn = True
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)
# Get all possible parts from both interpretations @classmethod
all_parts = set(posix_path.parts) | set(windows_path.parts) 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: # Get all possible parts from both interpretations
if part.startswith("."): all_parts = set(posix_path.parts) | set(windows_path.parts)
return False
if part in {"dist", "build", "__pycache__", "venv", "coverage.xml"}:
return False
if part.endswith(".lock"):
return False
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

View file

@ -1,6 +1,6 @@
[project] [project]
name = "arcade-core" name = "arcade-core"
version = "2.2.1" version = "2.2.2"
description = "Arcade Core - Core library for Arcade platform" description = "Arcade Core - Core library for Arcade platform"
readme = "README.md" readme = "README.md"
license = {text = "MIT"} license = {text = "MIT"}

View file

@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch
import pytest import pytest
from arcade_core.errors import ToolkitLoadError from arcade_core.errors import ToolkitLoadError
from arcade_core.toolkit import Toolkit, valid_path from arcade_core.toolkit import Toolkit, Validate
class TestToolkit: class TestToolkit:
@ -400,7 +400,7 @@ class TestValidateFile:
class TestValidPath: 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( @pytest.mark.parametrize(
"path_input", "path_input",
@ -452,80 +452,25 @@ class TestValidPath:
Path("file.py"), Path("file.py"),
Path("src/module.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): def test_valid_paths(self, path_input):
"""Test that valid paths are accepted.""" """Test that valid paths are accepted."""
assert valid_path(path_input) is True assert Validate.path(path_input) is True
@pytest.mark.parametrize( @pytest.mark.parametrize(
"path_input", "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) # Excluded directories (exact matches)
"dist", "dist",
"build", "build",
"__pycache__", "__pycache__",
"venv",
"coverage.xml", "coverage.xml",
# Paths containing excluded directories # Paths containing excluded directories
"dist/bundle.js", "dist/bundle.js",
"build/output/file.py", "build/output/file.py",
"src/__pycache__/module.cpython-39.pyc", "src/__pycache__/module.cpython-39.pyc",
"venv/lib/python3.9/site-packages/module.py",
"project/build/artifacts/file.py", "project/build/artifacts/file.py",
"lib/dist/package/module.py", "lib/dist/package/module.py",
"tools/venv/bin/python",
# Lock files (ending with .lock) # Lock files (ending with .lock)
"package.lock", "package.lock",
"poetry.lock", "poetry.lock",
@ -539,28 +484,19 @@ class TestValidPath:
"frontend/yarn.lock", "frontend/yarn.lock",
"backend/poetry.lock", "backend/poetry.lock",
"deep/nested/path/file.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 # Windows-style paths that should be invalid
"dist\\bundle.js", "dist\\bundle.js",
".hidden\\file.py",
"src\\package.lock", "src\\package.lock",
# Absolute paths that should be invalid # Absolute paths that should be invalid
"/home/user/project/.hidden/file.py",
"/home/user/project/dist/file.py", "/home/user/project/dist/file.py",
"/opt/project/package.lock", "/opt/project/package.lock",
# Unicode with exclusion patterns # Unicode with exclusion patterns
".файл", # Hidden Cyrillic file
"文件.lock", # Chinese lock file "文件.lock", # Chinese lock file
# Path objects # Path objects
Path(".hidden/file.py"),
Path("dist/bundle.js"), Path("dist/bundle.js"),
Path("package.lock"), Path("package.lock"),
], ],
) )
def test_invalid_paths(self, path_input): def test_invalid_paths(self, path_input):
"""Test that invalid paths are rejected.""" """Test that invalid paths are rejected."""
assert valid_path(path_input) is False assert Validate.path(path_input) is False

View file

@ -1,6 +1,6 @@
[project] [project]
name = "arcade-ai" name = "arcade-ai"
version = "2.1.3" version = "2.1.4"
description = "Arcade.dev - Tool Calling platform for Agents" description = "Arcade.dev - Tool Calling platform for Agents"
readme = "README.md" readme = "README.md"
license = {file = "LICENSE"} license = {file = "LICENSE"}
@ -21,7 +21,7 @@ requires-python = ">=3.10"
dependencies = [ dependencies = [
# CLI dependencies # CLI dependencies
"arcade-core>=2.2.1,<3.0.0", "arcade-core>=2.2.2,<3.0.0",
"typer==0.10.0", "typer==0.10.0",
"rich==13.9.4", "rich==13.9.4",
"Jinja2==3.1.6", "Jinja2==3.1.6",