Repository: pypa/setuptools (python)

Issue:
pkg_resources.Distribution should provide __dir__
pkg_resources.Distribution uses `__getattr__` to proxy attributes to its _provider attribute; it should also implement its own `__dir__` so that (e.g.) tab-completion works correctly.

Pull request:
Feature/issue 1330
## Summary of changes

Add `__dir__()` implementation to `pkg_resources.Distribution()` that includes the attributes in the `_provider` instance variable.

Closes #1330

### Pull Request Checklist
- [x] Changes have tests
- [x] News fragment added in changelog.d. See [documentation](http://setuptools.readthedocs.io/en/latest/developer-guide.html#making-a-pull-request) for details


Patch:
diff --git a/changelog.d/1359.change.rst b/changelog.d/1359.change.rst
deleted file mode 100644
index 05746e737..000000000
--- a/changelog.d/1359.change.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Support using "file:" to load a PEP 440-compliant package version
-from a text file.
diff --git a/changelog.d/1364.rst b/changelog.d/1364.rst
new file mode 100644
index 000000000..f7b4c0129
--- /dev/null
+++ b/changelog.d/1364.rst
@@ -0,0 +1,1 @@
+Add `__dir__()` implementation to `pkg_resources.Distribution()` that includes the attributes in the `_provider` instance variable.
\ No newline at end of file
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index f7b9351b7..76830e413 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -2424,7 +2424,7 @@ Metadata
 Key                             Aliases            Type
 ==============================  =================  =====
 name                                               str
-version                                            attr:, file:, str
+version                                            attr:, str
 url                             home-page          str
 download_url                    download-url       str
 project_urls                                       dict
@@ -2444,10 +2444,6 @@ requires                                           list-comma
 obsoletes                                          list-comma
 ==============================  =================  =====
 
-.. note::
-    A version loaded using the ``file:`` directive must comply with PEP 440.
-    It is easy to accidentally put something other than a valid version
-    string in such a file, so validation is stricter in this case.
 
 Options
 -------
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index d5b0fe982..394930ca1 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -2667,6 +2667,19 @@ class Distribution(object):
             raise AttributeError(attr)
         return getattr(self._provider, attr)
 
+    def __dir__(self):
+        return list(
+            set(super(Distribution, self).__dir__())
+            | set(
+                attr for attr in self._provider.__dir__()
+                if not attr.startswith('_')
+            )
+        )
+
+    if not hasattr(object, '__dir__'):
+        # python 2.7 not supported
+        del __dir__
+
     @classmethod
     def from_filename(cls, filename, metadata=None, **kw):
         return cls.from_location(
diff --git a/setuptools/config.py b/setuptools/config.py
index 6343840e2..8eddcae88 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -7,7 +7,6 @@ from functools import partial
 from importlib import import_module
 
 from distutils.errors import DistutilsOptionError, DistutilsFileError
-from setuptools.extern.packaging.version import LegacyVersion, parse
 from setuptools.extern.six import string_types
 
 
@@ -428,18 +427,6 @@ class ConfigMetadataHandler(ConfigHandler):
         :rtype: str
 
         """
-        version = self._parse_file(value)
-
-        if version != value:
-            version = version.strip()
-            # Be strict about versions loaded from file because it's easy to
-            # accidentally include newlines and other unintended content
-            if isinstance(parse(version), LegacyVersion):
-                raise DistutilsOptionError('Version loaded from %s does not comply with PEP 440: %s' % (
-                    value, version
-                ))
-            return version
-
         version = self._parse_attr(value)
 
         if callable(version):


Test patch:
diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py
index 05f35adef..04d02c1f6 100644
--- a/pkg_resources/tests/test_resources.py
+++ b/pkg_resources/tests/test_resources.py
@@ -145,6 +145,35 @@ class TestDistro:
         for v in "Twisted>=1.5", "Twisted>=1.5\nZConfig>=2.0":
             self.checkRequires(self.distRequires(v), v)
 
+    needs_object_dir = pytest.mark.skipif(
+        not hasattr(object, '__dir__'),
+        reason='object.__dir__ necessary for self.__dir__ implementation',
+    )
+
+    def test_distribution_dir(self):
+        d = pkg_resources.Distribution()
+        dir(d)
+
+    @needs_object_dir
+    def test_distribution_dir_includes_provider_dir(self):
+        d = pkg_resources.Distribution()
+        before = d.__dir__()
+        assert 'test_attr' not in before
+        d._provider.test_attr = None
+        after = d.__dir__()
+        assert len(after) == len(before) + 1
+        assert 'test_attr' in after
+
+    @needs_object_dir
+    def test_distribution_dir_ignores_provider_dir_leading_underscore(self):
+        d = pkg_resources.Distribution()
+        before = d.__dir__()
+        assert '_test_attr' not in before
+        d._provider._test_attr = None
+        after = d.__dir__()
+        assert len(after) == len(before)
+        assert '_test_attr' not in after
+
     def testResolve(self):
         ad = pkg_resources.Environment([])
         ws = WorkingSet([])
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 17ac09c8f..abb953a8f 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -268,23 +268,6 @@ class TestMetadata:
         with get_dist(tmpdir) as dist:
             assert dist.metadata.version == '2016.11.26'
 
-    def test_version_file(self, tmpdir):
-
-        _, config = fake_env(
-            tmpdir,
-            '[metadata]\n'
-            'version = file: fake_package/version.txt\n'
-        )
-        tmpdir.join('fake_package', 'version.txt').write('1.2.3\n')
-
-        with get_dist(tmpdir) as dist:
-            assert dist.metadata.version == '1.2.3'
-
-        tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n')
-        with pytest.raises(DistutilsOptionError):
-            with get_dist(tmpdir) as dist:
-                _ = dist.metadata.version
-
     def test_unknown_meta_item(self, tmpdir):
 
         fake_env(