Skip to content

Commit 56a8b90

Browse files
committed
Warn about packages/modules included as package data
1 parent ed5d4bf commit 56a8b90

File tree

1 file changed

+54
-2
lines changed

1 file changed

+54
-2
lines changed

setuptools/command/build_py.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import distutils.errors
99
import itertools
1010
import stat
11+
import warnings
12+
from pathlib import Path
13+
from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
1114
from setuptools.extern.more_itertools import unique_everseen
1215

1316

@@ -129,6 +132,7 @@ def analyze_manifest(self):
129132
src_dirs[assert_relative(self.get_package_dir(package))] = package
130133

131134
self.run_command('egg_info')
135+
checker = _IncludePackageDataAbuse()
132136
ei_cmd = self.get_finalized_command('egg_info')
133137
for path in ei_cmd.filelist.files:
134138
d, f = os.path.split(assert_relative(path))
@@ -139,8 +143,13 @@ def analyze_manifest(self):
139143
d, df = os.path.split(d)
140144
f = os.path.join(df, f)
141145
if d in src_dirs:
142-
if path.endswith('.py') and f == oldf:
143-
continue # it's a module, not data
146+
if f == oldf:
147+
if checker.is_module(f):
148+
continue # it's a module, not data
149+
else:
150+
importable = checker.importable_item(src_dirs[d], f)
151+
if importable:
152+
checker.warn(importable)
144153
mf.setdefault(src_dirs[d], []).append(path)
145154

146155
def get_data_files(self):
@@ -240,3 +249,46 @@ def assert_relative(path):
240249
% path
241250
)
242251
raise DistutilsSetupError(msg)
252+
253+
254+
class _IncludePackageDataAbuse:
255+
"""Inform users that package or module is included as 'data file'"""
256+
257+
MESSAGE = """\
258+
!!\n\n
259+
###################################
260+
# Package/module would be ignored #
261+
###################################
262+
Python recognizes {importable!r} as an importable package or module, however
263+
it is included in the distribution as "data".
264+
This behavior is likely to change in future versions of setuptools (and
265+
therefore is considered deprecated).
266+
267+
Please make sure that {importable!r} is recognized as a package/module by using
268+
setuptools' `packages` configuration field or the proper package discovery methods.
269+
270+
To find more information, look for "package discovery" and "data files" on
271+
setuptools documentation page.
272+
\n\n!!
273+
"""
274+
275+
def __init__(self):
276+
self._already_warned = set()
277+
278+
def is_module(self, file):
279+
return file.endswith(".py") and file[:-len(".py")].isidentifier()
280+
281+
def importable_item(self, pkg, file):
282+
path = Path(file)
283+
parents = path.parent.parts
284+
module = [path.stem] if tuple(path.suffixes) == (".py",) else []
285+
parts = list(itertools.takewhile(str.isidentifier, [*parents, *module]))
286+
if parts:
287+
return ".".join([pkg, *parts])
288+
return None
289+
290+
def warn(self, importable):
291+
if importable not in self._already_warned:
292+
msg = textwrap.dedent(self.MESSAGE).format(importable=importable)
293+
warnings.warn(msg, SetuptoolsDeprecationWarning, stacklevel=2)
294+
self._already_warned.add(importable)

0 commit comments

Comments
 (0)