Skip to content

Commit

Permalink
Merge pull request #271 from pypa/feature/pathlib-data-files
Browse files Browse the repository at this point in the history
Allow path objects for data-files
  • Loading branch information
jaraco authored Jul 19, 2024
2 parents d6a581f + 4d50db3 commit 0fa94ff
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 41 deletions.
69 changes: 40 additions & 29 deletions distutils/command/install_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@

# contributed by Bastian Kleineidam

from __future__ import annotations

import functools
import os

from typing import Iterable

from ..core import Command
from ..util import change_root, convert_path

Expand Down Expand Up @@ -46,36 +51,42 @@ def finalize_options(self):
def run(self):
self.mkpath(self.install_dir)
for f in self.data_files:
if isinstance(f, str):
# it's a simple file, so copy it
f = convert_path(f)
if self.warn_dir:
self.warn(
"setup script did not provide a directory for "
f"'{f}' -- installing right in '{self.install_dir}'"
)
(out, _) = self.copy_file(f, self.install_dir)
self._copy(f)

@functools.singledispatchmethod
def _copy(self, f: tuple[str | os.PathLike, Iterable[str | os.PathLike]]):
# it's a tuple with path to install to and a list of files
dir = convert_path(f[0])
if not os.path.isabs(dir):
dir = os.path.join(self.install_dir, dir)
elif self.root:
dir = change_root(self.root, dir)
self.mkpath(dir)

if f[1] == []:
# If there are no files listed, the user must be
# trying to create an empty directory, so add the
# directory to the list of output files.
self.outfiles.append(dir)
else:
# Copy files, adding them to the list of output files.
for data in f[1]:
data = convert_path(data)
(out, _) = self.copy_file(data, dir)
self.outfiles.append(out)
else:
# it's a tuple with path to install to and a list of files
dir = convert_path(f[0])
if not os.path.isabs(dir):
dir = os.path.join(self.install_dir, dir)
elif self.root:
dir = change_root(self.root, dir)
self.mkpath(dir)

if f[1] == []:
# If there are no files listed, the user must be
# trying to create an empty directory, so add the
# directory to the list of output files.
self.outfiles.append(dir)
else:
# Copy files, adding them to the list of output files.
for data in f[1]:
data = convert_path(data)
(out, _) = self.copy_file(data, dir)
self.outfiles.append(out)

@_copy.register(str)
@_copy.register(os.PathLike)
def _(self, f: str | os.PathLike):
# it's a simple file, so copy it
f = convert_path(f)
if self.warn_dir:
self.warn(
"setup script did not provide a directory for "
f"'{f}' -- installing right in '{self.install_dir}'"
)
(out, _) = self.copy_file(f, self.install_dir)
self.outfiles.append(out)

def get_inputs(self):
return self.data_files or []
Expand Down
31 changes: 20 additions & 11 deletions distutils/tests/test_install_data.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Tests for distutils.command.install_data."""

import os
from distutils.command.install_data import install_data
from distutils.tests import support
import pathlib

import pytest

from distutils.command.install_data import install_data
from distutils.tests import support


@pytest.mark.usefixtures('save_env')
class TestInstallData(
Expand All @@ -18,22 +20,27 @@ def test_simple_run(self):

# data_files can contain
# - simple files
# - a Path object
# - a tuple with a path, and a list of file
one = os.path.join(pkg_dir, 'one')
self.write_file(one, 'xxx')
inst2 = os.path.join(pkg_dir, 'inst2')
two = os.path.join(pkg_dir, 'two')
self.write_file(two, 'xxx')
three = pathlib.Path(pkg_dir) / 'three'
self.write_file(three, 'xxx')

cmd.data_files = [one, (inst2, [two])]
assert cmd.get_inputs() == [one, (inst2, [two])]
cmd.data_files = [one, (inst2, [two]), three]
assert cmd.get_inputs() == [one, (inst2, [two]), three]

# let's run the command
cmd.ensure_finalized()
cmd.run()

# let's check the result
assert len(cmd.get_outputs()) == 2
assert len(cmd.get_outputs()) == 3
rthree = os.path.split(one)[-1]
assert os.path.exists(os.path.join(inst, rthree))
rtwo = os.path.split(two)[-1]
assert os.path.exists(os.path.join(inst2, rtwo))
rone = os.path.split(one)[-1]
Expand All @@ -46,21 +53,23 @@ def test_simple_run(self):
cmd.run()

# let's check the result
assert len(cmd.get_outputs()) == 2
assert len(cmd.get_outputs()) == 3
assert os.path.exists(os.path.join(inst, rthree))
assert os.path.exists(os.path.join(inst2, rtwo))
assert os.path.exists(os.path.join(inst, rone))
cmd.outfiles = []

# now using root and empty dir
cmd.root = os.path.join(pkg_dir, 'root')
inst4 = os.path.join(pkg_dir, 'inst4')
three = os.path.join(cmd.install_dir, 'three')
self.write_file(three, 'xx')
cmd.data_files = [one, (inst2, [two]), ('inst3', [three]), (inst4, [])]
inst5 = os.path.join(pkg_dir, 'inst5')
four = os.path.join(cmd.install_dir, 'four')
self.write_file(four, 'xx')
cmd.data_files = [one, (inst2, [two]), three, ('inst5', [four]), (inst5, [])]
cmd.ensure_finalized()
cmd.run()

# let's check the result
assert len(cmd.get_outputs()) == 4
assert len(cmd.get_outputs()) == 5
assert os.path.exists(os.path.join(inst, rthree))
assert os.path.exists(os.path.join(inst2, rtwo))
assert os.path.exists(os.path.join(inst, rone))
5 changes: 5 additions & 0 deletions distutils/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import email.policy
import io
import os
import pathlib
import sys
import sysconfig as stdlib_sysconfig
import unittest.mock as mock
Expand Down Expand Up @@ -72,6 +73,7 @@ def _join(path):
os.path.join = _join

assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff'
assert convert_path(pathlib.Path('/home/to/my/stuff')) == '/home/to/my/stuff'

# win
os.sep = '\\'
Expand All @@ -85,8 +87,11 @@ def _join(*path):
convert_path('/home/to/my/stuff')
with pytest.raises(ValueError):
convert_path('home/to/my/stuff/')
with pytest.raises(ValueError):
convert_path(pathlib.Path('/home/to/my/stuff'))

assert convert_path('home/to/my/stuff') == 'home\\to\\my\\stuff'
assert convert_path(pathlib.Path('home/to/my/stuff')) == 'home\\to\\my\\stuff'
assert convert_path('.') == os.curdir

def test_change_root(self):
Expand Down
11 changes: 10 additions & 1 deletion distutils/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
one of the other *util.py modules.
"""

from __future__ import annotations

import functools
import importlib.util
import os
Expand Down Expand Up @@ -116,7 +118,14 @@ def split_version(s):
return [int(n) for n in s.split('.')]


def convert_path(pathname):
def convert_path(pathname: str | os.PathLike) -> str:
"""
Allow for pathlib.Path inputs and then make native.
"""
return make_native(os.fspath(pathname))


def make_native(pathname: str) -> str:
"""Return 'pathname' as a name that will work on the native filesystem,
i.e. split it on '/' and put it back together again using the current
directory separator. Needed because filenames in the setup script are
Expand Down

0 comments on commit 0fa94ff

Please sign in to comment.