Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bc18532

Browse files
committed
Branch merge
2 parents a529308 + 6280606 commit bc18532

8 files changed

Lines changed: 156 additions & 77 deletions

File tree

Doc/library/collections.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ The class can be used to simulate nested scopes and is useful in templating.
8383
creating subcontexts that can be updated without altering values in any
8484
of the parent mappings.
8585

86-
.. attribute:: parents()
86+
.. method:: parents()
8787

8888
Returns a new :class:`ChainMap` containing all of the maps in the current
8989
instance except the first one. This is useful for skipping the first map

Doc/packaging/setupcfg.rst

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,19 @@ compilers
176176
compilers =
177177
hotcompiler.SmartCCompiler
178178

179-
setup_hook
180-
defines a callable that will be called right after the
181-
:file:`setup.cfg` file is read. The callable receives the configuration
182-
in form of a mapping and can make some changes to it. *optional*
179+
setup_hooks
180+
Defines a list of callables to be called right after the :file:`setup.cfg`
181+
file is read, before any other processing. The callables are executed in the
182+
order they're found in the file; if one of them cannot be found, tools should
183+
not stop, but for example produce a warning and continue with the next line.
184+
Each callable receives the configuration as a dictionary (keys are
185+
:file:`setup.cfg` sections, values are dictionaries of fields) and can make
186+
any changes to it. *optional*, *multi*
183187

184188
Example::
185189

186190
[global]
187-
setup_hook = package.setup.customize_dist
191+
setup_hooks = package.setup.customize_dist
188192

189193

190194
Metadata
@@ -285,6 +289,7 @@ One extra field not present in PEP 345 is supported:
285289

286290
description-file
287291
Path to a text file that will be used to fill the ``description`` field.
292+
Multiple values are accepted; they must be separated by whitespace.
288293
``description-file`` and ``description`` are mutually exclusive. *optional*
289294

290295

Lib/packaging/config.py

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from packaging import logger
1010
from packaging.errors import PackagingOptionError
1111
from packaging.compiler.extension import Extension
12-
from packaging.util import check_environ, iglob, resolve_name, strtobool
12+
from packaging.util import (check_environ, iglob, resolve_name, strtobool,
13+
split_multiline)
1314
from packaging.compiler import set_compiler
1415
from packaging.command import set_command
1516
from packaging.markers import interpret
@@ -60,17 +61,15 @@ def get_resources_dests(resources_root, rules):
6061

6162

6263
class Config:
63-
"""Reads configuration files and work with the Distribution instance
64-
"""
64+
"""Class used to work with configuration files"""
6565
def __init__(self, dist):
6666
self.dist = dist
67-
self.setup_hook = None
67+
self.setup_hooks = []
6868

69-
def run_hook(self, config):
70-
if self.setup_hook is None:
71-
return
72-
# the hook gets only the config
73-
self.setup_hook(config)
69+
def run_hooks(self, config):
70+
"""Run setup hooks in the order defined in the spec."""
71+
for hook in self.setup_hooks:
72+
hook(config)
7473

7574
def find_config_files(self):
7675
"""Find as many configuration files as should be processed for this
@@ -124,29 +123,26 @@ def _convert_metadata(self, name, value):
124123
# XXX
125124
return value
126125

127-
def _multiline(self, value):
128-
value = [v for v in
129-
[v.strip() for v in value.split('\n')]
130-
if v != '']
131-
return value
132-
133126
def _read_setup_cfg(self, parser, cfg_filename):
134127
cfg_directory = os.path.dirname(os.path.abspath(cfg_filename))
135128
content = {}
136129
for section in parser.sections():
137130
content[section] = dict(parser.items(section))
138131

139-
# global:setup_hook is called *first*
132+
# global setup hooks are called first
140133
if 'global' in content:
141-
if 'setup_hook' in content['global']:
142-
setup_hook = content['global']['setup_hook']
143-
try:
144-
self.setup_hook = resolve_name(setup_hook)
145-
except ImportError as e:
146-
logger.warning('could not import setup_hook: %s',
147-
e.args[0])
148-
else:
149-
self.run_hook(content)
134+
if 'setup_hooks' in content['global']:
135+
setup_hooks = split_multiline(content['global']['setup_hooks'])
136+
137+
for line in setup_hooks:
138+
try:
139+
hook = resolve_name(line)
140+
except ImportError as e:
141+
logger.warning('cannot find setup hook: %s', e.args[0])
142+
else:
143+
self.setup_hooks.append(hook)
144+
145+
self.run_hooks(content)
150146

151147
metadata = self.dist.metadata
152148

@@ -155,7 +151,7 @@ def _read_setup_cfg(self, parser, cfg_filename):
155151
for key, value in content['metadata'].items():
156152
key = key.replace('_', '-')
157153
if metadata.is_multi_field(key):
158-
value = self._multiline(value)
154+
value = split_multiline(value)
159155

160156
if key == 'project-url':
161157
value = [(label.strip(), url.strip())
@@ -168,21 +164,18 @@ def _read_setup_cfg(self, parser, cfg_filename):
168164
"mutually exclusive")
169165
raise PackagingOptionError(msg)
170166

171-
if isinstance(value, list):
172-
filenames = value
173-
else:
174-
filenames = value.split()
167+
filenames = value.split()
175168

176-
# concatenate each files
177-
value = ''
169+
# concatenate all files
170+
value = []
178171
for filename in filenames:
179172
# will raise if file not found
180173
with open(filename) as description_file:
181-
value += description_file.read().strip() + '\n'
174+
value.append(description_file.read().strip())
182175
# add filename as a required file
183176
if filename not in metadata.requires_files:
184177
metadata.requires_files.append(filename)
185-
value = value.strip()
178+
value = '\n'.join(value).strip()
186179
key = 'description'
187180

188181
if metadata.is_metadata_field(key):
@@ -192,7 +185,7 @@ def _read_setup_cfg(self, parser, cfg_filename):
192185
files = content['files']
193186
self.dist.package_dir = files.pop('packages_root', None)
194187

195-
files = dict((key, self._multiline(value)) for key, value in
188+
files = dict((key, split_multiline(value)) for key, value in
196189
files.items())
197190

198191
self.dist.packages = []
@@ -310,7 +303,7 @@ def parse_config_files(self, filenames=None):
310303
opt = opt.replace('-', '_')
311304

312305
if opt == 'sub_commands':
313-
val = self._multiline(val)
306+
val = split_multiline(val)
314307
if isinstance(val, str):
315308
val = [val]
316309

@@ -348,14 +341,14 @@ def parse_config_files(self, filenames=None):
348341
raise PackagingOptionError(msg)
349342

350343
def _load_compilers(self, compilers):
351-
compilers = self._multiline(compilers)
344+
compilers = split_multiline(compilers)
352345
if isinstance(compilers, str):
353346
compilers = [compilers]
354347
for compiler in compilers:
355348
set_compiler(compiler.strip())
356349

357350
def _load_commands(self, commands):
358-
commands = self._multiline(commands)
351+
commands = split_multiline(commands)
359352
if isinstance(commands, str):
360353
commands = [commands]
361354
for command in commands:

Lib/packaging/tests/test_config.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
compilers =
9191
packaging.tests.test_config.DCompiler
9292
93-
setup_hook = %(setup-hook)s
93+
setup_hooks = %(setup-hooks)s
9494
9595
9696
@@ -135,8 +135,16 @@ def __init__(self, *args):
135135
pass
136136

137137

138-
def hook(content):
139-
content['metadata']['version'] += '.dev1'
138+
def version_hook(config):
139+
config['metadata']['version'] += '.dev1'
140+
141+
142+
def first_hook(config):
143+
config['files']['modules'] += '\n first'
144+
145+
146+
def third_hook(config):
147+
config['files']['modules'] += '\n third'
140148

141149

142150
class FooBarBazTest:
@@ -186,7 +194,7 @@ def tearDown(self):
186194

187195
def write_setup(self, kwargs=None):
188196
opts = {'description-file': 'README', 'extra-files': '',
189-
'setup-hook': 'packaging.tests.test_config.hook'}
197+
'setup-hooks': 'packaging.tests.test_config.version_hook'}
190198
if kwargs:
191199
opts.update(kwargs)
192200
self.write_file('setup.cfg', SETUP_CFG % opts, encoding='utf-8')
@@ -318,16 +326,30 @@ def test_parse_extensions_in_config(self):
318326
self.assertEqual(ext.extra_compile_args, cargs)
319327
self.assertEqual(ext.language, 'cxx')
320328

321-
def test_missing_setuphook_warns(self):
322-
self.write_setup({'setup-hook': 'this.does._not.exist'})
329+
def test_missing_setup_hook_warns(self):
330+
self.write_setup({'setup-hooks': 'this.does._not.exist'})
323331
self.write_file('README', 'yeah')
324332
dist = self.get_dist()
325333
logs = self.get_logs(logging.WARNING)
326334
self.assertEqual(1, len(logs))
327-
self.assertIn('could not import setup_hook', logs[0])
335+
self.assertIn('cannot find setup hook', logs[0])
336+
337+
def test_multiple_setup_hooks(self):
338+
self.write_setup({
339+
'setup-hooks': '\n packaging.tests.test_config.first_hook'
340+
'\n packaging.tests.test_config.missing_hook'
341+
'\n packaging.tests.test_config.third_hook'
342+
})
343+
self.write_file('README', 'yeah')
344+
dist = self.get_dist()
345+
346+
self.assertEqual(['haven', 'first', 'third'], dist.py_modules)
347+
logs = self.get_logs(logging.WARNING)
348+
self.assertEqual(1, len(logs))
349+
self.assertIn('cannot find setup hook', logs[0])
328350

329351
def test_metadata_requires_description_files_missing(self):
330-
self.write_setup({'description-file': 'README\n README2'})
352+
self.write_setup({'description-file': 'README README2'})
331353
self.write_file('README', 'yeah')
332354
self.write_file('README2', 'yeah')
333355
os.mkdir('src')

Lib/packaging/tests/test_util.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
from io import StringIO
99

1010
from packaging.tests import support, unittest
11+
from packaging.tests.test_config import SETUP_CFG
1112
from packaging.errors import (
1213
PackagingPlatformError, PackagingByteCompileError, PackagingFileError,
1314
PackagingExecError, InstallationException)
1415
from packaging import util
16+
from packaging.dist import Distribution
1517
from packaging.util import (
1618
convert_path, change_root, split_quoted, strtobool, rfc822_escape,
1719
get_compiler_versions, _MAC_OS_X_LD_VERSION, byte_compile, find_packages,
1820
spawn, get_pypirc_path, generate_pypirc, read_pypirc, resolve_name, iglob,
1921
RICH_GLOB, egginfo_to_distinfo, is_setuptools, is_distutils, is_packaging,
20-
get_install_method)
22+
get_install_method, cfg_to_args)
2123

2224

2325
PYPIRC = """\
@@ -88,13 +90,15 @@ class UtilTestCase(support.EnvironRestorer,
8890
support.LoggingCatcher,
8991
unittest.TestCase):
9092

91-
restore_environ = ['HOME']
93+
restore_environ = ['HOME', 'PLAT']
9294

9395
def setUp(self):
9496
super(UtilTestCase, self).setUp()
95-
self.tmp_dir = self.mkdtemp()
96-
self.rc = os.path.join(self.tmp_dir, '.pypirc')
97-
os.environ['HOME'] = self.tmp_dir
97+
self.addCleanup(os.chdir, os.getcwd())
98+
tempdir = self.mkdtemp()
99+
self.rc = os.path.join(tempdir, '.pypirc')
100+
os.environ['HOME'] = tempdir
101+
os.chdir(tempdir)
98102
# saving the environment
99103
self.name = os.name
100104
self.platform = sys.platform
@@ -103,7 +107,6 @@ def setUp(self):
103107
self.join = os.path.join
104108
self.isabs = os.path.isabs
105109
self.splitdrive = os.path.splitdrive
106-
#self._config_vars = copy(sysconfig._config_vars)
107110

108111
# patching os.uname
109112
if hasattr(os, 'uname'):
@@ -137,7 +140,6 @@ def tearDown(self):
137140
os.uname = self.uname
138141
else:
139142
del os.uname
140-
#sysconfig._config_vars = copy(self._config_vars)
141143
util.find_executable = self.old_find_executable
142144
subprocess.Popen = self.old_popen
143145
sys.old_stdout = self.old_stdout
@@ -491,6 +493,38 @@ def test_server_empty_registration(self):
491493
content = f.read()
492494
self.assertEqual(content, WANTED)
493495

496+
def test_cfg_to_args(self):
497+
opts = {'description-file': 'README', 'extra-files': '',
498+
'setup-hooks': 'packaging.tests.test_config.version_hook'}
499+
self.write_file('setup.cfg', SETUP_CFG % opts)
500+
self.write_file('README', 'loooong description')
501+
502+
args = cfg_to_args()
503+
# use Distribution to get the contents of the setup.cfg file
504+
dist = Distribution()
505+
dist.parse_config_files()
506+
metadata = dist.metadata
507+
508+
self.assertEqual(args['name'], metadata['Name'])
509+
# + .dev1 because the test SETUP_CFG also tests a hook function in
510+
# test_config.py for appending to the version string
511+
self.assertEqual(args['version'] + '.dev1', metadata['Version'])
512+
self.assertEqual(args['author'], metadata['Author'])
513+
self.assertEqual(args['author_email'], metadata['Author-Email'])
514+
self.assertEqual(args['maintainer'], metadata['Maintainer'])
515+
self.assertEqual(args['maintainer_email'],
516+
metadata['Maintainer-Email'])
517+
self.assertEqual(args['description'], metadata['Summary'])
518+
self.assertEqual(args['long_description'], metadata['Description'])
519+
self.assertEqual(args['classifiers'], metadata['Classifier'])
520+
self.assertEqual(args['requires'], metadata['Requires-Dist'])
521+
self.assertEqual(args['provides'], metadata['Provides-Dist'])
522+
523+
self.assertEqual(args['package_dir'].get(''), dist.package_dir)
524+
self.assertEqual(args['packages'], dist.packages)
525+
self.assertEqual(args['scripts'], dist.scripts)
526+
self.assertEqual(args['py_modules'], dist.py_modules)
527+
494528

495529
class GlobTestCaseBase(support.TempdirManager,
496530
support.LoggingCatcher,

0 commit comments

Comments
 (0)