Imported Upstream version 46.4.0 upstream/46.4.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 29 Dec 2020 22:06:13 +0000 (07:06 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 29 Dec 2020 22:06:13 +0000 (07:06 +0900)
.bumpversion.cfg
CHANGES.rst
docs/developer-guide.txt
docs/requirements.txt
docs/setuptools.txt
setup.cfg
setuptools/config.py
setuptools/tests/test_config.py

index 4ee9218515947cb867a223b06a96d5d81e7cf452..72d02f2b592342f86419f2aba3ee62a9320ea3bf 100644 (file)
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 46.3.1
+current_version = 46.4.0
 commit = True
 tag = True
 
index fd3c16ba055dab0bc2b71d9ab3a5a598f9ac7e9e..ea667028a215605e60644921433215f3885a49bd 100644 (file)
@@ -1,3 +1,12 @@
+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
 -------
 
index 0b4ae4d4c37937c515c45ff44ecae895f208cced..e6171e4e30eeacd28be402459f8725f0e7bda2b2 100644 (file)
@@ -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
index 6c35bf646e2dbf7650525050667c79eb555892c7..104d68faef16a03729f97978992a29cefef33630 100644 (file)
@@ -2,3 +2,6 @@
 sphinx
 jaraco.packaging>=6.1
 rst.linker>=1.9
+pygments-github-lexers==0.0.5
+
+setuptools>=34
index 30a30c2612d4b62f544055dc4747b207e05f2691..c37b7ec5c50340c04a73c0fe32c4add5b2c7b213 100644 (file)
@@ -2193,11 +2193,54 @@ Metadata and options are set in the config sections of the same name.
 * In some cases, complex values can be provided in dedicated subsections for
   clarity.
 
-* Some keys allow ``file:``, ``attr:``, and ``find:`` and ``find_namespace:`` directives in
+* Some keys allow ``file:``, ``attr:``, ``find:``, and ``find_namespace:`` directives in
   order to cover common usecases.
 
 * Unknown keys are ignored.
 
+setup.cfg-only projects
+=======================
+
+.. versionadded:: 40.9.0
+
+If ``setup.py`` is missing from the project directory when a :pep:`517`
+build is invoked, ``setuptools`` emulates a dummy ``setup.py`` file containing
+only a ``setuptools.setup()`` call.
+
+.. note::
+
+    :pep:`517` doesn't support editable installs so this is currently
+    incompatible with ``pip install -e .``, as :pep:`517` does not support editable installs.
+
+This means that you can have a Python project with all build configuration
+specified in ``setup.cfg``, without a ``setup.py`` file, if you **can rely
+on** your project always being built by a :pep:`517`/:pep:`518` compatible
+frontend.
+
+To use this feature:
+
+* Specify build requirements and :pep:`517` build backend in
+  ``pyproject.toml``.
+  For example:
+
+  .. code-block:: toml
+
+      [build-system]
+      requires = [
+        "setuptools >= 40.9.0",
+        "wheel",
+      ]
+      build-backend = "setuptools.build_meta"
+
+* Use a :pep:`517` compatible build frontend, such as ``pip >= 19`` or ``pep517``.
+
+  .. warning::
+
+      As :pep:`517` is new, support is not universal, and frontends that
+      do support it may still have bugs. For compatibility, you may want to
+      put a ``setup.py`` file containing only a ``setuptools.setup()``
+      invocation.
+
 
 Using a ``src/`` layout
 =======================
@@ -2247,6 +2290,12 @@ Special directives:
 
 * ``attr:`` - Value is read from a module attribute.  ``attr:`` supports
   callables and iterables; unsupported types are cast using ``str()``.
+
+  In order to support the common case of a literal value assigned to a variable
+  in a module containing (directly or indirectly) third-party imports,
+  ``attr:`` first tries to read the value from the module by examining the
+  module's AST.  If that fails, ``attr:`` falls back to importing the module.
+
 * ``file:`` - Value is read from a list of files and then concatenated
 
 
index 467d1fa7237a76de434a02a905b10218a7481d56..72d4dce92c1f323a12a27d012b613028ac009ea4 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -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
@@ -78,3 +78,4 @@ docs =
     sphinx
     jaraco.packaging>=6.1
     rst.linker>=1.9
+    pygments-github-lexers==0.0.5
index 9b9a0c45e756b44ddea7660228934d0a37fcd97c..45df2e3f2e83f5552f8f1f8da31476ab40aff26d 100644 (file)
@@ -1,14 +1,16 @@
 from __future__ import absolute_import, unicode_literals
+import ast
 import io
 import os
 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
@@ -19,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.
@@ -344,15 +384,16 @@ class ConfigHandler:
             elif '' in package_dir:
                 # A custom parent directory was specified for all root modules
                 parent_path = os.path.join(os.getcwd(), package_dir[''])
-        sys.path.insert(0, parent_path)
-        try:
-            module = import_module(module_name)
-            value = getattr(module, attr_name)
 
-        finally:
-            sys.path = sys.path[1:]
+        with patch_path(parent_path):
+            try:
+                # 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 value
+        return getattr(module, attr_name)
 
     @classmethod
     def _get_parser_compound(cls, *parse_methods):
index 2fa0b374e265dd421413840da352a5685a650fa5..67992c041f72e6706bd82afd9aa7d1064dd27bc9 100644 (file)
@@ -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
 
 
@@ -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,13 +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.subpkg_a.mod.VERSION\n'
+        )
+        with get_dist(tmpdir) as dist:
+            assert dist.metadata.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'