diff --git a/arcade/resources/__init__.py b/arcade/resources/__init__.py index b506e338c..84702037e 100644 --- a/arcade/resources/__init__.py +++ b/arcade/resources/__init__.py @@ -50,7 +50,19 @@ def resolve_resource_path(path: str | Path) -> Path: return resolve(path) -def resolve(path: str | Path) -> Path: +def create_path(path: Path) -> None: + """ + Create a file or directory at the given path. + If the path has a suffix, it's treated as a file, otherwise, as a directory. + """ + if path.suffix: + path.parent.mkdir(parents=True, exist_ok=True) + path.touch(exist_ok=True) + else: + path.mkdir(parents=True, exist_ok=True) + + +def resolve(path: str | Path, *, create: bool = False) -> Path: """ Attempts to resolve a path to a resource including resource handles. @@ -67,6 +79,7 @@ def resolve(path: str | Path) -> Path: Args: path: A Path or string + create: If True, create the path if it doesn't exist. """ # Convert to a Path object and resolve resource handle if isinstance(path, str): @@ -87,22 +100,30 @@ def resolve(path: str | Path) -> Path: # match. This allows for overriding of resources. paths = get_resource_handle_paths(handle) for handle_path in reversed(paths): - path = handle_path / resource - if path.exists(): + candidate_path = handle_path / resource + if candidate_path.exists(): + path = candidate_path break else: - searched_paths = "\n".join(f"-> {p}" for p in reversed(paths)) - raise FileNotFoundError( - f"Cannot locate resource '{resource}' using handle " - f"'{handle}' in any of the following paths:\n" - f"{searched_paths}" - ) + if create: + path = paths[-1] / resource + create_path(path) + else: + searched_paths = "\n".join(f"-> {p}" for p in reversed(paths)) + raise FileNotFoundError( + f"Cannot locate resource '{resource}' using handle " + f"'{handle}' in any of the following paths:\n" + f"{searched_paths}" + ) # Always convert into a Path object - path = Path(handle_path / resource) + path = Path(path) else: path = Path(path) + if create: + create_path(path) + try: path = Path(path.resolve(strict=True)) except AttributeError: diff --git a/tests/unit/resources/test_handles.py b/tests/unit/resources/test_handles.py index 6fa242e32..f6bb50998 100644 --- a/tests/unit/resources/test_handles.py +++ b/tests/unit/resources/test_handles.py @@ -18,6 +18,36 @@ def test_default_handles(): resources.resolve(":system:images/cards/cardBack_blue1.png") +def test_resolve_create(tmp_path): + """Test if we can create directories and files using the resolve.""" + # Test directory creation + new_dir = tmp_path / "created_dir" + assert not new_dir.exists() + result_dir = resources.resolve(new_dir, create=True) + assert result_dir == new_dir.resolve() + assert new_dir.exists() and new_dir.is_dir() + + # Test file creation + new_file = tmp_path / "created_file.txt" + assert not new_file.exists() + result_file = resources.resolve(new_file, create=True) + assert result_file == new_file.resolve() + assert new_file.exists() and new_file.is_file() + + +def test_default_handle_create(): + """Test if we can create directories and files using the default handle.""" + handle_dir = ":resources:new_dir" + with pytest.raises(FileNotFoundError): + resources.resolve(handle_dir) + result_dir = resources.resolve(handle_dir, create=True) + assert result_dir.exists() and result_dir.is_dir() + for base_path in resources.get_resource_handle_paths("resources"): + dir_path = base_path / "new_dir" + if dir_path.exists() and dir_path.is_dir(): + dir_path.rmdir() + + def test_add_handles(monkeypatch): monkeypatch.setattr(resources, "handles", {})