summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/packaging.compiler.rst4
-rw-r--r--Doc/packaging/setupscript.rst4
-rw-r--r--Lib/packaging/tests/test_support.py78
-rw-r--r--Lib/packaging/tests/test_util.py34
-rw-r--r--Lib/packaging/util.py56
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS2
7 files changed, 161 insertions, 18 deletions
diff --git a/Doc/library/packaging.compiler.rst b/Doc/library/packaging.compiler.rst
index d771972..89b6e45 100644
--- a/Doc/library/packaging.compiler.rst
+++ b/Doc/library/packaging.compiler.rst
@@ -675,3 +675,7 @@ extension modules.
| | abort the build process, but | |
| | simply skip the extension. | |
+------------------------+--------------------------------+---------------------------+
+
+To distribute extension modules that live in a package (e.g. ``package.ext``),
+you need to create you need to create a :file:`{package}/__init__.py` file to
+let Python recognize and import your module.
diff --git a/Doc/packaging/setupscript.rst b/Doc/packaging/setupscript.rst
index dbac3dd..cafde20 100644
--- a/Doc/packaging/setupscript.rst
+++ b/Doc/packaging/setupscript.rst
@@ -177,6 +177,10 @@ resulting object code are identical in both cases; the only difference is where
in the filesystem (and therefore where in Python's namespace hierarchy) the
resulting extension lives.
+If your distribution contains only one or more extension modules in a package,
+you need to create a :file:`{package}/__init__.py` file anyway, otherwise Python
+won't be able to import anything.
+
If you have a number of extensions all in the same package (or all under the
same base package), use the :option:`ext_package` keyword argument to
:func:`setup`. For example, ::
diff --git a/Lib/packaging/tests/test_support.py b/Lib/packaging/tests/test_support.py
new file mode 100644
index 0000000..0ae9e1b
--- /dev/null
+++ b/Lib/packaging/tests/test_support.py
@@ -0,0 +1,78 @@
+import os
+import tempfile
+
+from packaging.dist import Distribution
+from packaging.tests import support, unittest
+
+
+class TestingSupportTestCase(unittest.TestCase):
+
+ def test_fake_dec(self):
+ @support.fake_dec(1, 2, k=3)
+ def func(arg0, *args, **kargs):
+ return arg0, args, kargs
+ self.assertEqual(func(-1, -2, k=-3), (-1, (-2,), {'k': -3}))
+
+ def test_TempdirManager(self):
+ files = {}
+
+ class Tester(support.TempdirManager, unittest.TestCase):
+
+ def test_mktempfile(self2):
+ tmpfile = self2.mktempfile()
+ files['test_mktempfile'] = tmpfile.name
+ self.assertTrue(os.path.isfile(tmpfile.name))
+
+ def test_mkdtemp(self2):
+ tmpdir = self2.mkdtemp()
+ files['test_mkdtemp'] = tmpdir
+ self.assertTrue(os.path.isdir(tmpdir))
+
+ def test_write_file(self2):
+ tmpdir = self2.mkdtemp()
+ files['test_write_file'] = tmpdir
+ self2.write_file((tmpdir, 'file1'), 'me file 1')
+ file1 = os.path.join(tmpdir, 'file1')
+ self.assertTrue(os.path.isfile(file1))
+ text = ''
+ with open(file1, 'r') as f:
+ text = f.read()
+ self.assertEqual(text, 'me file 1')
+
+ def test_create_dist(self2):
+ project_dir, dist = self2.create_dist()
+ files['test_create_dist'] = project_dir
+ self.assertTrue(os.path.isdir(project_dir))
+ self.assertIsInstance(dist, Distribution)
+
+ def test_assertIsFile(self2):
+ fd, fn = tempfile.mkstemp()
+ os.close(fd)
+ self.addCleanup(support.unlink, fn)
+ self2.assertIsFile(fn)
+ self.assertRaises(AssertionError, self2.assertIsFile, 'foO')
+
+ def test_assertIsNotFile(self2):
+ tmpdir = self2.mkdtemp()
+ self2.assertIsNotFile(tmpdir)
+
+ tester = Tester()
+ for name in ('test_mktempfile', 'test_mkdtemp', 'test_write_file',
+ 'test_create_dist', 'test_assertIsFile',
+ 'test_assertIsNotFile'):
+ tester.setUp()
+ try:
+ getattr(tester, name)()
+ finally:
+ tester.tearDown()
+
+ # check clean-up
+ if name in files:
+ self.assertFalse(os.path.exists(files[name]))
+
+
+def test_suite():
+ return unittest.makeSuite(TestingSupportTestCase)
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="test_suite")
diff --git a/Lib/packaging/tests/test_util.py b/Lib/packaging/tests/test_util.py
index 92aa72a..1cf5c93 100644
--- a/Lib/packaging/tests/test_util.py
+++ b/Lib/packaging/tests/test_util.py
@@ -5,11 +5,10 @@ import time
import logging
import tempfile
import textwrap
+import warnings
import subprocess
from io import StringIO
-from packaging.tests import support, unittest
-from packaging.tests.test_config import SETUP_CFG
from packaging.errors import (
PackagingPlatformError, PackagingByteCompileError, PackagingFileError,
PackagingExecError, InstallationException)
@@ -20,7 +19,11 @@ from packaging.util import (
get_compiler_versions, _MAC_OS_X_LD_VERSION, byte_compile, find_packages,
spawn, get_pypirc_path, generate_pypirc, read_pypirc, resolve_name, iglob,
RICH_GLOB, egginfo_to_distinfo, is_setuptools, is_distutils, is_packaging,
- get_install_method, cfg_to_args, encode_multipart)
+ get_install_method, cfg_to_args, generate_setup_py, encode_multipart)
+
+from packaging.tests import support, unittest
+from packaging.tests.test_config import SETUP_CFG
+from test.script_helper import assert_python_ok, assert_python_failure
PYPIRC = """\
@@ -513,7 +516,9 @@ class UtilTestCase(support.EnvironRestorer,
self.write_file('setup.cfg', SETUP_CFG % opts, encoding='utf-8')
self.write_file('README', 'loooong description')
- args = cfg_to_args()
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', DeprecationWarning)
+ args = cfg_to_args()
# use Distribution to get the contents of the setup.cfg file
dist = Distribution()
dist.parse_config_files()
@@ -539,6 +544,26 @@ class UtilTestCase(support.EnvironRestorer,
self.assertEqual(args['scripts'], dist.scripts)
self.assertEqual(args['py_modules'], dist.py_modules)
+ def test_generate_setup_py(self):
+ # undo subprocess.Popen monkey-patching before using assert_python_*
+ subprocess.Popen = self.old_popen
+ os.chdir(self.mkdtemp())
+ self.write_file('setup.cfg', textwrap.dedent("""\
+ [metadata]
+ name = SPAM
+ classifier = Programming Language :: Python
+ """))
+ generate_setup_py()
+ self.assertTrue(os.path.exists('setup.py'), 'setup.py not created')
+ rc, out, err = assert_python_ok('setup.py', '--name')
+ self.assertEqual(out, b'SPAM\n')
+ self.assertEqual(err, b'')
+
+ # a generated setup.py should complain if no setup.cfg is present
+ os.unlink('setup.cfg')
+ rc, out, err = assert_python_failure('setup.py', '--name')
+ self.assertIn(b'setup.cfg', err)
+
def test_encode_multipart(self):
fields = [('username', 'wok'), ('password', 'secret')]
files = [('picture', 'wok.png', b'PNG89')]
@@ -590,7 +615,6 @@ class GlobTestCase(GlobTestCaseBase):
super(GlobTestCase, self).tearDown()
def assertGlobMatch(self, glob, spec):
- """"""
tempdir = self.build_files_tree(spec)
expected = self.clean_tree(spec)
os.chdir(tempdir)
diff --git a/Lib/packaging/util.py b/Lib/packaging/util.py
index 2af1149..8dd715f 100644
--- a/Lib/packaging/util.py
+++ b/Lib/packaging/util.py
@@ -6,6 +6,7 @@ import csv
import imp
import sys
import errno
+import codecs
import shutil
import string
import hashlib
@@ -417,7 +418,8 @@ byte_compile(files, optimize=%r, force=%r,
# cfile - byte-compiled file
# dfile - purported source filename (same as 'file' by default)
if optimize >= 0:
- cfile = imp.cache_from_source(file, debug_override=not optimize)
+ cfile = imp.cache_from_source(file,
+ debug_override=not optimize)
else:
cfile = imp.cache_from_source(file)
dfile = file
@@ -931,6 +933,24 @@ def _iglob(path_glob):
yield file
+# HOWTO change cfg_to_args
+#
+# This function has two major constraints: It is copied by inspect.getsource
+# in generate_setup_py; it is used in generated setup.py which may be run by
+# any Python version supported by distutils2 (2.4-3.3).
+#
+# * Keep objects like D1_D2_SETUP_ARGS static, i.e. in the function body
+# instead of global.
+# * If you use a function from another module, update the imports in
+# SETUP_TEMPLATE. Use only modules, classes and functions compatible with
+# all versions: codecs.open instead of open, RawConfigParser.readfp instead
+# of read, standard exceptions instead of Packaging*Error, etc.
+# * If you use a function from this module, update the template and
+# generate_setup_py.
+#
+# test_util tests this function and the generated setup.py, but does not test
+# that it's compatible with all Python versions.
+
def cfg_to_args(path='setup.cfg'):
"""Compatibility helper to use setup.cfg in setup.py.
@@ -941,8 +961,6 @@ def cfg_to_args(path='setup.cfg'):
*file* is the path to the setup.cfg file. If it doesn't exist,
PackagingFileError is raised.
"""
- # We need to declare the following constants here so that it's easier to
- # generate the setup.py afterwards, using inspect.getsource.
# XXX ** == needs testing
D1_D2_SETUP_ARGS = {"name": ("metadata",),
@@ -986,10 +1004,11 @@ def cfg_to_args(path='setup.cfg'):
# The real code starts here
config = RawConfigParser()
- if not os.path.exists(path):
- raise PackagingFileError("file '%s' does not exist" %
- os.path.abspath(path))
- config.read(path, encoding='utf-8')
+ f = codecs.open(path, encoding='utf-8')
+ try:
+ config.readfp(f)
+ finally:
+ f.close()
kwargs = {}
for arg in D1_D2_SETUP_ARGS:
@@ -1011,8 +1030,11 @@ def cfg_to_args(path='setup.cfg'):
filenames = split_multiline(filenames)
in_cfg_value = []
for filename in filenames:
- with open(filename) as fp:
+ fp = codecs.open(filename, encoding='utf-8')
+ try:
in_cfg_value.append(fp.read())
+ finally:
+ fp.close()
in_cfg_value = '\n\n'.join(in_cfg_value)
else:
continue
@@ -1029,13 +1051,19 @@ def cfg_to_args(path='setup.cfg'):
return kwargs
-_SETUP_TMPL = """\
+SETUP_TEMPLATE = """\
# This script was automatically generated by packaging
import os
+import codecs
from distutils.core import setup
-from ConfigParser import RawConfigParser
+try:
+ from ConfigParser import RawConfigParser
+except ImportError:
+ from configparser import RawConfigParser
+
+%(split_multiline)s
-%(func)s
+%(cfg_to_args)s
setup(**cfg_to_args())
"""
@@ -1049,8 +1077,10 @@ def generate_setup_py():
if os.path.exists("setup.py"):
raise PackagingFileError("a setup.py file already exists")
+ source = SETUP_TEMPLATE % {'split_multiline': getsource(split_multiline),
+ 'cfg_to_args': getsource(cfg_to_args)}
with open("setup.py", "w", encoding='utf-8') as fp:
- fp.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)})
+ fp.write(source)
# Taken from the pip project
@@ -1307,6 +1337,8 @@ def get_install_method(path):
def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
preserve_symlinks=False, update=False, verbose=True,
dry_run=False):
+ # FIXME use of this function is why we get spurious logging message on
+ # stdout when tests run; kill and replace by shuil!
from distutils.file_util import copy_file
if not dry_run and not os.path.isdir(src):
diff --git a/Misc/ACKS b/Misc/ACKS
index 6d3a6d9..08531b9 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -56,6 +56,7 @@ Nicolas Bareil
Chris Barker
Nick Barnes
Quentin Barnes
+David Barnett
Richard Barran
Cesar Eduardo Barros
Des Barry
diff --git a/Misc/NEWS b/Misc/NEWS
index 36aafd9..55b261e 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -505,7 +505,7 @@ Library
in os.kill().
- Add support for unary plus and unary minus to collections.Counter().
- Issue #13121: Also an support for inplace math operators.
+ Issue #13121: Also add support for inplace math operators.
- Issue #12683: urlparse updated to include svn as schemes that uses relative
paths. (svn from 1.5 onwards support relative path).