From 6337cbd8de512d7cce5959298f9f01dfc76b005a Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Sun, 1 Oct 2017 13:21:03 -0300 Subject: [PATCH] Update pip's pkg_resources from Setuptools 34.2.0. This is basically a port of pip's commit eaccb88674beff75bb98a1d1e2d53a26a9c63890, which updates the vendored pkg_resources, and fixes a crash which, apparently, seems to happen when trying to import setuptools/pkg_resources in an environment where a namespace package is installed, but where there is more than one directory in the path of the namespace package. This crash is reproducible by running any pipenv command, including `pipenv --version` in an empty directory. See the upstream bug here[1] for further details. Fixes #552 [1] https://github.com/pypa/setuptools/issues/885 --- .../pip/_vendor/pkg_resources/__init__.py | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/pipenv/patched/pip/_vendor/pkg_resources/__init__.py b/pipenv/patched/pip/_vendor/pkg_resources/__init__.py index b8e598b9..b4bf0cae 100644 --- a/pipenv/patched/pip/_vendor/pkg_resources/__init__.py +++ b/pipenv/patched/pip/_vendor/pkg_resources/__init__.py @@ -74,13 +74,8 @@ __import__('pip._vendor.packaging.specifiers') __import__('pip._vendor.packaging.requirements') __import__('pip._vendor.packaging.markers') - if (3, 0) < sys.version_info < (3, 3): - msg = ( - "Support for Python 3.0-3.2 has been dropped. Future versions " - "will fail here." - ) - warnings.warn(msg) + raise RuntimeError("Python 3.3 or later is required") # declare some globals that will be defined later to # satisfy the linters. @@ -791,7 +786,7 @@ class WorkingSet(object): self._added_new(dist) def resolve(self, requirements, env=None, installer=None, - replace_conflicting=False): + replace_conflicting=False, extras=None): """List all distributions needed to (recursively) meet `requirements` `requirements` must be a sequence of ``Requirement`` objects. `env`, @@ -807,6 +802,12 @@ class WorkingSet(object): the wrong version. Otherwise, if an `installer` is supplied it will be invoked to obtain the correct version of the requirement and activate it. + + `extras` is a list of the extras to be used with these requirements. + This is important because extra requirements may look like `my_req; + extra = "my_extra"`, which would otherwise be interpreted as a purely + optional requirement. Instead, we want to be able to assert that these + requirements are truly required. """ # set up the stack @@ -830,7 +831,7 @@ class WorkingSet(object): # Ignore cyclic or redundant dependencies continue - if not req_extras.markers_pass(req): + if not req_extras.markers_pass(req, extras): continue dist = best.get(req.key) @@ -1009,7 +1010,7 @@ class _ReqExtras(dict): Map each requirement to the extras that demanded it. """ - def markers_pass(self, req): + def markers_pass(self, req, extras=None): """ Evaluate markers for req against each extra that demanded it. @@ -1019,7 +1020,7 @@ class _ReqExtras(dict): """ extra_evals = ( req.marker.evaluate({'extra': extra}) - for extra in self.get(req, ()) + (None,) + for extra in self.get(req, ()) + (extras or (None,)) ) return not req.marker or any(extra_evals) @@ -1956,6 +1957,12 @@ def find_eggs_in_zip(importer, path_item, only=False): subpath = os.path.join(path_item, subitem) for dist in find_eggs_in_zip(zipimport.zipimporter(subpath), subpath): yield dist + elif subitem.lower().endswith('.dist-info'): + subpath = os.path.join(path_item, subitem) + submeta = EggMetadata(zipimport.zipimporter(subpath)) + submeta.egg_info = subpath + yield Distribution.from_location(path_item, subitem, submeta) + register_finder(zipimport.zipimporter, find_eggs_in_zip) @@ -2118,6 +2125,10 @@ def _rebuild_mod_path(orig_path, package_name, module): parts = path_parts[:-module_parts] return safe_sys_path_index(_normalize_cached(os.sep.join(parts))) + if not isinstance(orig_path, list): + # Is this behavior useful when module.__path__ is not a list? + return + orig_path.sort(key=position_in_sys_path) module.__path__[:] = [_normalize_cached(p) for p in orig_path] @@ -2304,8 +2315,14 @@ class EntryPoint(object): def require(self, env=None, installer=None): if self.extras and not self.dist: raise UnknownExtra("Can't require() without a distribution", self) + + # Get the requirements for this entry point with all its extras and + # then resolve them. We have to pass `extras` along when resolving so + # that the working set knows what extras we want. Otherwise, for + # dist-info distributions, the working set will assume that the + # requirements for that extra are purely optional and skip over them. reqs = self.dist.requires(self.extras) - items = working_set.resolve(reqs, env, installer) + items = working_set.resolve(reqs, env, installer, extras=self.extras) list(map(working_set.add, items)) pattern = re.compile( @@ -3010,9 +3027,11 @@ def _initialize(g=globals()): "Set up global resource manager (deliberately not state-saved)" manager = ResourceManager() g['_manager'] = manager - for name in dir(manager): - if not name.startswith('_'): - g[name] = getattr(manager, name) + g.update( + (name, getattr(manager, name)) + for name in dir(manager) + if not name.startswith('_') + ) @_call_aside @@ -3041,10 +3060,10 @@ def _initialize_master_working_set(): # ensure that all distributions added to the working set in the future # (e.g. by calling ``require()``) will get activated as well, # with higher priority (replace=True). - dist = None # ensure dist is defined for del dist below - for dist in working_set: + tuple( dist.activate(replace=False) - del dist + for dist in working_set + ) add_activation_listener(lambda dist: dist.activate(replace=True), existing=False) working_set.entries = [] # match order