Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/anthropic/lib/_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@


def files_from_dir(directory: str | os.PathLike[str]) -> list[FileTypes]:
path = Path(directory)
# the names carry the directory itself, so where it sits has to be known:
# `.` has no parent to take the name from, and `..` would put one in a name
path = Path(os.path.abspath(directory))

files: list[FileTypes] = []
_collect_files(path, path.parent, files)
Expand All @@ -26,7 +28,7 @@ def _collect_files(directory: Path, relative_to: Path, files: list[FileTypes]) -


async def async_files_from_dir(directory: str | os.PathLike[str]) -> list[FileTypes]:
path = anyio.Path(directory)
path = anyio.Path(os.path.abspath(directory))

files: list[FileTypes] = []
await _async_collect_files(path, path.parent, files)
Expand Down
53 changes: 53 additions & 0 deletions tests/lib/test_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from __future__ import annotations

import os
from pathlib import Path

import pytest

from anthropic.lib import files_from_dir, async_files_from_dir


@pytest.fixture
def skill(tmp_path: Path) -> Path:
directory = tmp_path / "greeting"
(directory / "scripts").mkdir(parents=True)
(directory / "SKILL.md").write_text("# hi\n")
(directory / "scripts" / "run.py").write_text("print(1)\n")
return directory


def _names(files: list[object]) -> list[str]:
return sorted(name for name, *_ in files) # type: ignore[misc]


EXPECTED = ["greeting/SKILL.md", "greeting/scripts/run.py"]


def test_names_carry_the_directory(skill: Path) -> None:
assert _names(files_from_dir(skill)) == EXPECTED


RELATIVE = [("inside", "."), ("inside", "./"), ("inside", "../greeting"), ("parent", "greeting")]


def test_a_relative_root_names_the_same_files(skill: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# the names go to the API as given, so `.` must not drop the directory and
# `..` must not reach a name
for where, spelling in RELATIVE:
monkeypatch.chdir(skill if where == "inside" else skill.parent)
assert _names(files_from_dir(spelling)) == EXPECTED, spelling


def test_a_trailing_separator_changes_nothing(skill: Path) -> None:
assert _names(files_from_dir(f"{skill}{os.sep}")) == EXPECTED


async def test_async_names_carry_the_directory(skill: Path) -> None:
assert _names(await async_files_from_dir(skill)) == EXPECTED


async def test_async_a_relative_root_names_the_same_files(skill: Path, monkeypatch: pytest.MonkeyPatch) -> None:
for where, spelling in RELATIVE:
monkeypatch.chdir(skill if where == "inside" else skill.parent)
assert _names(await async_files_from_dir(spelling)) == EXPECTED, spelling