diff --git a/betty/cache.py b/betty/cache.py
index 1e77fafc7..813ef7f38 100644
--- a/betty/cache.py
+++ b/betty/cache.py
@@ -1,6 +1,7 @@
"""
Provide the Cache API.
"""
+import asyncio
import logging
import shutil
from contextlib import suppress
@@ -22,5 +23,5 @@ def __init__(self, localizer: Localizer):
async def clear(self) -> None:
with suppress(FileNotFoundError):
- shutil.rmtree(fs.CACHE_DIRECTORY_PATH)
+ await asyncio.to_thread(shutil.rmtree, fs.CACHE_DIRECTORY_PATH)
logging.getLogger(__name__).info(self._localizer._('All caches cleared.'))
diff --git a/betty/extension/cotton_candy/__init__.py b/betty/extension/cotton_candy/__init__.py
index 2e9e0f600..b642eadb1 100644
--- a/betty/extension/cotton_candy/__init__.py
+++ b/betty/extension/cotton_candy/__init__.py
@@ -3,6 +3,7 @@
"""
from __future__ import annotations
+import asyncio
import logging
import re
from collections import defaultdict
@@ -224,8 +225,8 @@ async def npm_build(self, working_directory_path: Path, assets_directory_path: P
async def _copy_npm_build(self, source_directory_path: Path, destination_directory_path: Path) -> None:
await makedirs(destination_directory_path, exist_ok=True)
- copy2(source_directory_path / 'cotton_candy.css', destination_directory_path / 'cotton_candy.css')
- copy2(source_directory_path / 'cotton_candy.js', destination_directory_path / 'cotton_candy.js')
+ await asyncio.to_thread(copy2, source_directory_path / 'cotton_candy.css', destination_directory_path / 'cotton_candy.css')
+ await asyncio.to_thread(copy2, source_directory_path / 'cotton_candy.js', destination_directory_path / 'cotton_candy.js')
async def generate(self, task_context: GenerationContext) -> None:
assets_directory_path = await self.app.extensions[_Npm].ensure_assets(self)
diff --git a/betty/extension/http_api_doc/__init__.py b/betty/extension/http_api_doc/__init__.py
index 77696647c..83d78466a 100644
--- a/betty/extension/http_api_doc/__init__.py
+++ b/betty/extension/http_api_doc/__init__.py
@@ -1,6 +1,7 @@
"""Integrate Betty with `ReDoc `_."""
from __future__ import annotations
+import asyncio
import logging
from pathlib import Path
from shutil import copy2
@@ -21,7 +22,7 @@ def depends_on(cls) -> set[type[Extension]]:
async def npm_build(self, working_directory_path: Path, assets_directory_path: Path) -> None:
await self.app.extensions[_Npm].install(type(self), working_directory_path)
- copy2(working_directory_path / 'node_modules' / 'redoc' / 'bundles' / 'redoc.standalone.js', assets_directory_path / 'http-api-doc.js')
+ await asyncio.to_thread(copy2, working_directory_path / 'node_modules' / 'redoc' / 'bundles' / 'redoc.standalone.js', assets_directory_path / 'http-api-doc.js')
logging.getLogger(__name__).info(self._app.localizer._('Built the HTTP API documentation.'))
@classmethod
@@ -31,7 +32,7 @@ def npm_cache_scope(cls) -> CacheScope:
async def generate(self, task_context: GenerationContext) -> None:
assets_directory_path = await self.app.extensions[_Npm].ensure_assets(self)
await makedirs(self.app.project.configuration.www_directory_path, exist_ok=True)
- copy2(assets_directory_path / 'http-api-doc.js', self.app.project.configuration.www_directory_path / 'http-api-doc.js')
+ await asyncio.to_thread(copy2, assets_directory_path / 'http-api-doc.js', self.app.project.configuration.www_directory_path / 'http-api-doc.js')
@classmethod
def assets_directory_path(cls) -> Path | None:
diff --git a/betty/extension/maps/__init__.py b/betty/extension/maps/__init__.py
index 392cda9da..0c68b19f7 100644
--- a/betty/extension/maps/__init__.py
+++ b/betty/extension/maps/__init__.py
@@ -1,6 +1,7 @@
"""Integrate Betty with `Leaflet.js `_."""
from __future__ import annotations
+import asyncio
import logging
from contextlib import suppress
from pathlib import Path
@@ -29,10 +30,10 @@ async def npm_build(self, working_directory_path: Path, assets_directory_path: P
async def _copy_npm_build(self, source_directory_path: Path, destination_directory_path: Path) -> None:
await makedirs(destination_directory_path, exist_ok=True)
- copy2(source_directory_path / 'maps.css', destination_directory_path / 'maps.css')
- copy2(source_directory_path / 'maps.js', destination_directory_path / 'maps.js')
+ await asyncio.to_thread(copy2, source_directory_path / 'maps.css', destination_directory_path / 'maps.css')
+ await asyncio.to_thread(copy2, source_directory_path / 'maps.js', destination_directory_path / 'maps.js')
with suppress(FileNotFoundError):
- copytree(source_directory_path / 'images', destination_directory_path / 'images')
+ await asyncio.to_thread(copytree, source_directory_path / 'images', destination_directory_path / 'images')
@classmethod
def npm_cache_scope(cls) -> CacheScope:
diff --git a/betty/extension/npm/__init__.py b/betty/extension/npm/__init__.py
index 735b9ee9b..6998c38e0 100644
--- a/betty/extension/npm/__init__.py
+++ b/betty/extension/npm/__init__.py
@@ -5,6 +5,7 @@
"""
from __future__ import annotations
+import asyncio
import logging
import os
import shutil
@@ -168,7 +169,7 @@ async def _build_assets_to_directory_path(extension: NpmBuilder & Extension, ass
assert isinstance(extension, Extension)
assert isinstance(extension, NpmBuilder)
with suppress(FileNotFoundError):
- shutil.rmtree(assets_directory_path)
+ await asyncio.to_thread(shutil.rmtree, assets_directory_path)
os.makedirs(assets_directory_path)
async with TemporaryDirectory() as working_directory_path_str:
working_directory_path = Path(working_directory_path_str)
@@ -202,7 +203,8 @@ async def install(self, extension_type: type[NpmBuilder & Extension], working_di
if self._npm_requirement:
self._npm_requirement.assert_met()
- shutil.copytree(
+ await asyncio.to_thread(
+ shutil.copytree,
_get_assets_src_directory_path(extension_type),
working_directory_path,
dirs_exist_ok=True,
diff --git a/betty/extension/trees/__init__.py b/betty/extension/trees/__init__.py
index 87538a701..848ac928c 100644
--- a/betty/extension/trees/__init__.py
+++ b/betty/extension/trees/__init__.py
@@ -1,6 +1,7 @@
"""Provide interactive family trees by integrating Betty with `Cytoscape.js `_."""
from __future__ import annotations
+import asyncio
import logging
import subprocess
from pathlib import Path
@@ -29,8 +30,8 @@ async def npm_build(self, working_directory_path: Path, assets_directory_path: P
async def _copy_npm_build(self, source_directory_path: Path, destination_directory_path: Path) -> None:
await makedirs(destination_directory_path, exist_ok=True)
- copy2(source_directory_path / 'trees.css', destination_directory_path / 'trees.css')
- copy2(source_directory_path / 'trees.js', destination_directory_path / 'trees.js')
+ await asyncio.to_thread(copy2, source_directory_path / 'trees.css', destination_directory_path / 'trees.css')
+ await asyncio.to_thread(copy2, source_directory_path / 'trees.js', destination_directory_path / 'trees.js')
@classmethod
def npm_cache_scope(cls) -> CacheScope:
diff --git a/betty/fs.py b/betty/fs.py
index 88d99911d..aa5609e34 100644
--- a/betty/fs.py
+++ b/betty/fs.py
@@ -93,7 +93,7 @@ def open(self, *file_paths: Path) -> _Open:
async def copy2(self, source_path: Path, destination_path: Path) -> Path:
for fs_path, _ in self._paths:
with suppress(FileNotFoundError):
- copy2(fs_path / source_path, destination_path)
+ await asyncio.to_thread(copy2, fs_path / source_path, destination_path)
return destination_path
tried_paths = [str(fs_path / source_path) for fs_path, _ in self._paths]
raise FileNotFoundError('Could not find any of %s.' % ', '.join(tried_paths))
diff --git a/betty/generate.py b/betty/generate.py
index 0140b704f..a3309b895 100644
--- a/betty/generate.py
+++ b/betty/generate.py
@@ -3,6 +3,7 @@
"""
from __future__ import annotations
+import asyncio
import json
import logging
import multiprocessing
@@ -164,7 +165,7 @@ async def generate(app: App) -> None:
task_context = GenerationContext(app)
with suppress(FileNotFoundError):
- shutil.rmtree(app.project.configuration.output_directory_path)
+ await asyncio.to_thread(shutil.rmtree, app.project.configuration.output_directory_path)
await aiofiles_os.makedirs(app.project.configuration.output_directory_path, exist_ok=True)
logger.info(app.localizer._('Generating your site to {output_directory}.').format(output_directory=app.project.configuration.output_directory_path))
diff --git a/betty/os.py b/betty/os.py
index 08b145c70..487d18532 100644
--- a/betty/os.py
+++ b/betty/os.py
@@ -3,6 +3,7 @@
"""
from __future__ import annotations
+import asyncio
import os
import shutil
from contextlib import suppress
@@ -26,7 +27,7 @@ async def link_or_copy(source_path: Path, destination_path: Path) -> None:
await link(source_path, destination_path)
except OSError:
with suppress(shutil.SameFileError):
- shutil.copyfile(source_path, destination_path)
+ await asyncio.to_thread(shutil.copyfile, source_path, destination_path)
class ChDir: