From d0836ddedc50a9b8f33149953490eb00f26aca3e Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 12 Oct 2026 10:42:20 -0700 Subject: [PATCH] Remove shutil.copytree backport Not necessary since Python 3.8 --- datasette/utils/__init__.py | 2 +- datasette/utils/shutil_backport.py | 102 ----------------------------- 2 files changed, 1 insertion(+), 103 deletions(-) delete mode 100644 datasette/utils/shutil_backport.py diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py index 669d04c7..ad39025f 100644 --- a/datasette/utils/__init__.py +++ b/datasette/utils/__init__.py @@ -19,6 +19,7 @@ import urllib from collections import Counter, OrderedDict, namedtuple from collections.abc import Iterable from contextlib import contextmanager +from shutil import copytree import aiofiles import click @@ -26,7 +27,6 @@ import markupsafe import mergedeep import yaml -from .shutil_backport import copytree from .sqlite import sqlite3, supports_table_xinfo if typing.TYPE_CHECKING: diff --git a/datasette/utils/shutil_backport.py b/datasette/utils/shutil_backport.py deleted file mode 100644 index d323f5d6..00000000 --- a/datasette/utils/shutil_backport.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Backported from Python 3.8. - -This code is licensed under the Python License: -https://github.com/python/cpython/blob/v3.8.3/LICENSE -""" - -import os -from shutil import Error, copy, copy2, copystat - - -def _copytree( - entries, - src, - dst, - symlinks, - ignore, - copy_function, - ignore_dangling_symlinks, - dirs_exist_ok=False, -): - if ignore is not None: - ignored_names = ignore(src, set(os.listdir(src))) - else: - ignored_names = set() - - os.makedirs(dst, exist_ok=dirs_exist_ok) - errors = [] - use_srcentry = copy_function is copy2 or copy_function is copy - - for srcentry in entries: - if srcentry.name in ignored_names: - continue - srcname = os.path.join(src, srcentry.name) - dstname = os.path.join(dst, srcentry.name) - srcobj = srcentry if use_srcentry else srcname - try: - if srcentry.is_symlink(): - linkto = os.readlink(srcname) - if symlinks: - os.symlink(linkto, dstname) - copystat(srcobj, dstname, follow_symlinks=not symlinks) - else: - if not os.path.exists(linkto) and ignore_dangling_symlinks: - continue - if srcentry.is_dir(): - copytree( - srcobj, - dstname, - symlinks, - ignore, - copy_function, - dirs_exist_ok=dirs_exist_ok, - ) - else: - copy_function(srcobj, dstname) - elif srcentry.is_dir(): - copytree( - srcobj, - dstname, - symlinks, - ignore, - copy_function, - dirs_exist_ok=dirs_exist_ok, - ) - else: - copy_function(srcentry, dstname) - except Error as err: - errors.extend(err.args[0]) - except OSError as why: - errors.append((srcname, dstname, str(why))) - try: - copystat(src, dst) - except OSError as why: - # Copying file access times may fail on Windows - if getattr(why, "winerror", None) is None: - errors.append((src, dst, str(why))) - if errors: - raise Error(errors) - return dst - - -def copytree( - src, - dst, - symlinks=False, - ignore=None, - copy_function=copy2, - ignore_dangling_symlinks=False, - dirs_exist_ok=False, -): - with os.scandir(src) as entries: - return _copytree( - entries=entries, - src=src, - dst=dst, - symlinks=symlinks, - ignore=ignore, - copy_function=copy_function, - ignore_dangling_symlinks=ignore_dangling_symlinks, - dirs_exist_ok=dirs_exist_ok, - )