Fix venv files not being found (#525)
This commit is contained in:
parent
21f892c0a0
commit
7888dc505e
5 changed files with 34 additions and 91 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue