Commit f852d159 authored by Jason R. Coombs's avatar Jason R. Coombs

Merge commit '52c394c1' into feature/2093-docs-revamp

parents 412d2e17 52c394c1
[bumpversion]
current_version = 46.3.1
current_version = 46.4.0
commit = True
tag = True
......
v46.4.0
-------
* #1753: ``attr:`` now extracts variables through rudimentary examination of the AST,
thereby supporting modules with third-party imports. If examining the AST
fails to find the variable, ``attr:`` falls back to the old behavior of
importing the module. Works on Python 3 only.
v46.3.1
-------
......
Document all supported keywords by migrating the ones from distutils.
``attr:`` now extracts variables through rudimentary examination of the AST,
thereby supporting modules with third-party imports. If examining the AST
fails to find the variable, ``attr:`` falls back to the old behavior of
importing the module.
......@@ -139,7 +139,7 @@ Vendored Dependencies
---------------------
Setuptools has some dependencies, but due to `bootstrapping issues
<https://github.com/pypa/setuptools/issues/980>`, those dependencies
<https://github.com/pypa/setuptools/issues/980>`_, those dependencies
cannot be declared as they won't be resolved soon enough to build
setuptools from source. Eventually, this limitation may be lifted as
PEP 517/518 reach ubiquitous adoption, but for now, Setuptools
......
......@@ -521,7 +521,7 @@ result (which must be a ``unittest.TestSuite``) is added to the tests to be
run. If the named suite is a package, any submodules and subpackages are
recursively added to the overall test suite. (Note: if your project specifies
a ``test_loader``, the rules for processing the chosen ``test_suite`` may
differ; see the `test_loader`_ documentation for more details.)
differ; see the :ref:`test_loader <test_loader>` documentation for more details.)
Note that many test systems including ``doctest`` support wrapping their
non-``unittest`` tests in ``TestSuite`` objects. So, if you are using a test
......@@ -563,4 +563,4 @@ The ``upload`` command was deprecated in version 40.0 and removed in version
For more information on the current best practices in uploading your packages
to PyPI, see the Python Packaging User Guide's "Packaging Python Projects"
tutorial specifically the section on `uploading the distribution archives
<https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives>`_.
\ No newline at end of file
<https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives>`_.
......@@ -16,7 +16,7 @@ formats = zip
[metadata]
name = setuptools
version = 46.3.1
version = 46.4.0
description = Easily download, build, install, upgrade, and uninstall Python packages
author = Python Packaging Authority
author_email = distutils-sig@python.org
......
......@@ -6,10 +6,11 @@ import sys
import warnings
import functools
import importlib
from collections import defaultdict
from functools import partial
from functools import wraps
from importlib import import_module
import contextlib
from distutils.errors import DistutilsOptionError, DistutilsFileError
from setuptools.extern.packaging.version import LegacyVersion, parse
......@@ -20,6 +21,44 @@ from setuptools.extern.six import string_types, PY3
__metaclass__ = type
class StaticModule:
"""
Attempt to load the module by the name
"""
def __init__(self, name):
spec = importlib.util.find_spec(name)
with open(spec.origin) as strm:
src = strm.read()
module = ast.parse(src)
vars(self).update(locals())
del self.self
def __getattr__(self, attr):
try:
return next(
ast.literal_eval(statement.value)
for statement in self.module.body
if isinstance(statement, ast.Assign)
for target in statement.targets
if isinstance(target, ast.Name) and target.id == attr
)
except Exception:
raise AttributeError(
"{self.name} has no attribute {attr}".format(**locals()))
@contextlib.contextmanager
def patch_path(path):
"""
Add path to front of sys.path for the duration of the context.
"""
try:
sys.path.insert(0, path)
yield
finally:
sys.path.remove(path)
def read_configuration(
filepath, find_others=False, ignore_option_errors=False):
"""Read given configuration file and returns options from it as a dict.
......@@ -346,50 +385,15 @@ class ConfigHandler:
# A custom parent directory was specified for all root modules
parent_path = os.path.join(os.getcwd(), package_dir[''])
fpath = os.path.join(parent_path, *module_name.split('.'))
if os.path.exists(fpath + '.py'):
fpath += '.py'
elif os.path.isdir(fpath):
fpath = os.path.join(fpath, '__init__.py')
else:
raise DistutilsOptionError('Could not find module ' + module_name)
with open(fpath, 'rb') as fp:
src = fp.read()
found = False
top_level = ast.parse(src)
for statement in top_level.body:
if isinstance(statement, ast.Assign):
for target in statement.targets:
if isinstance(target, ast.Name) \
and target.id == attr_name:
try:
value = ast.literal_eval(statement.value)
except ValueError:
found = False
else:
found = True
elif isinstance(target, ast.Tuple) \
and any(isinstance(t, ast.Name) and t.id == attr_name
for t in target.elts):
try:
stmnt_value = ast.literal_eval(statement.value)
except ValueError:
found = False
else:
for t, v in zip(target.elts, stmnt_value):
if isinstance(t, ast.Name) \
and t.id == attr_name:
value = v
found = True
if not found:
# Fall back to extracting attribute via importing
sys.path.insert(0, parent_path)
with patch_path(parent_path):
try:
module = import_module(module_name)
value = getattr(module, attr_name)
finally:
sys.path = sys.path[1:]
return value
# attempt to load value statically
return getattr(StaticModule(module_name), attr_name)
except Exception:
# fallback to simple import
module = importlib.import_module(module_name)
return getattr(module, attr_name)
@classmethod
def _get_parser_compound(cls, *parse_methods):
......
......@@ -2,6 +2,7 @@
from __future__ import unicode_literals
import contextlib
import pytest
from distutils.errors import DistutilsOptionError, DistutilsFileError
......@@ -9,6 +10,7 @@ from mock import patch
from setuptools.dist import Distribution, _Distribution
from setuptools.config import ConfigHandler, read_configuration
from setuptools.extern.six.moves import configparser
from setuptools.extern import six
from . import py2_only, py3_only
from .textwrap import DALS
......@@ -53,6 +55,7 @@ def fake_env(
' return [3, 4, 5, "dev"]\n'
'\n'
)
return package_dir, config
......@@ -103,7 +106,7 @@ class TestConfigurationReader:
'version = attr: none.VERSION\n'
'keywords = one, two\n'
)
with pytest.raises(DistutilsOptionError):
with pytest.raises(ImportError):
read_configuration('%s' % config)
config_dict = read_configuration(
......@@ -267,11 +270,23 @@ class TestMetadata:
def test_version(self, tmpdir):
_, config = fake_env(
package_dir, config = fake_env(
tmpdir,
'[metadata]\n'
'version = attr: fake_package.VERSION\n'
)
sub_a = package_dir.mkdir('subpkg_a')
sub_a.join('__init__.py').write('')
sub_a.join('mod.py').write('VERSION = (2016, 11, 26)')
sub_b = package_dir.mkdir('subpkg_b')
sub_b.join('__init__.py').write('')
sub_b.join('mod.py').write(
'import third_party_module\n'
'VERSION = (2016, 11, 26)'
)
with get_dist(tmpdir) as dist:
assert dist.metadata.version == '1.2.3'
......@@ -289,25 +304,20 @@ class TestMetadata:
with get_dist(tmpdir) as dist:
assert dist.metadata.version == '1'
subpack = tmpdir.join('fake_package').mkdir('subpackage')
subpack.join('__init__.py').write('')
subpack.join('submodule.py').write('VERSION = (2016, 11, 26)')
config.write(
'[metadata]\n'
'version = attr: fake_package.subpackage.submodule.VERSION\n'
'version = attr: fake_package.subpkg_a.mod.VERSION\n'
)
with get_dist(tmpdir) as dist:
assert dist.metadata.version == '2016.11.26'
subpack.join('submodule.py').write(
'import third_party_module\n'
'VERSION = (2016, 11, 26)'
)
if six.PY2:
# static version loading is unsupported on Python 2
return
config.write(
'[metadata]\n'
'version = attr: fake_package.subpackage.submodule.VERSION\n'
'version = attr: fake_package.subpkg_b.mod.VERSION\n'
)
with get_dist(tmpdir) as dist:
assert dist.metadata.version == '2016.11.26'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment