Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle legacy loaders for pip patch #1691

Merged
merged 2 commits into from
Mar 6, 2020
Merged

Conversation

gaborbernat
Copy link
Contributor

@gaborbernat gaborbernat commented Mar 5, 2020

Resolves #1690.

@asottile can you check that this solves your problem?

Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net>
@ofek
Copy link
Sponsor Contributor

ofek commented Mar 5, 2020

Any update?

@gaborbernat
Copy link
Contributor Author

@ofek are you affected, can you check that the pr fixes your issue?

@ofek
Copy link
Sponsor Contributor

ofek commented Mar 6, 2020

Not affected, just curious 🙂

@asottile
Copy link
Contributor

asottile commented Mar 6, 2020

it appears the functions take different arguments:

Traceback (most recent call last):
  File "/tmp/pymonkey/testing/pkg1/patchingmod_main.py", line 8, in <module>
    exit(main())
  File "/tmp/pymonkey/pymonkey.py", line 270, in entry
    tuple(patches) + ('--', original_entry_point) + tuple(argv)
  File "/tmp/pymonkey/pymonkey.py", line 258, in main
    return entry.load()()
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2445, in load
    return self.resolve()
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2451, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "/tmp/pymonkey/testing/pkg2/targetmod.py", line 4, in <module>
    import setuptools  # Some weird import hook
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/setuptools/__init__.py", line 5, in <module>
    import distutils.core
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "/usr/lib/python3.6/distutils/core.py", line 16, in <module>
    from distutils.dist import Distribution
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 62, in exec_module
    old(module)
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 62, in exec_module
    old(module)
  File "/usr/lib/python3.6/distutils/dist.py", line 18, in <module>
    from distutils.fancy_getopt import FancyGetopt, translate_longopt
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 62, in exec_module
    old(module)
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "/usr/lib/python3.6/distutils/fancy_getopt.py", line 12, in <module>
    import getopt
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 63, in exec_module
    patch_dist(module)
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 18, in patch_dist
    old_parse_config_files = dist.Distribution.parse_config_files
AttributeError: 'str' object has no attribute 'Distribution'

this gets ~closer:

$ diff -u /tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py{.old,}
--- /tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py.old	2020-03-05 19:10:41.798224966 -0800
+++ /tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py	2020-03-05 19:11:56.546224966 -0800
@@ -62,12 +62,17 @@
                                 old(module)
                                 patch_dist(module)
 
+                            def load_module(*args, **kwargs):
+                                module = old(*args, **kwargs)
+                                patch_dist(module)
+                                return module
+
                             if hasattr(loader, "exec_module"):
                                 old = loader.exec_module
                                 loader.exec_module = exec_module
                             else:
                                 old = loader.load_module
-                                loader.load_module = exec_module
+                                loader.load_module = load_module
                             return spec
                     finally:
                         self.fullname = None

however that patch never gets undone and mutates a singleton causing other modules to fail:

Traceback (most recent call last):
  File "/tmp/pymonkey/testing/pkg1/patchingmod_main.py", line 8, in <module>
    exit(main())
  File "/tmp/pymonkey/pymonkey.py", line 270, in entry
    tuple(patches) + ('--', original_entry_point) + tuple(argv)
  File "/tmp/pymonkey/pymonkey.py", line 258, in main
    return entry.load()()
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2445, in load
    return self.resolve()
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2451, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "/tmp/pymonkey/testing/pkg2/targetmod.py", line 4, in <module>
    import setuptools  # Some weird import hook
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/setuptools/__init__.py", line 5, in <module>
    import distutils.core
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "/usr/lib/python3.6/distutils/core.py", line 16, in <module>
    from distutils.dist import Distribution
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 66, in load_module
    module = old(*args, **kwargs)
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 62, in exec_module
    old(module)
  File "/usr/lib/python3.6/distutils/dist.py", line 18, in <module>
    from distutils.fancy_getopt import FancyGetopt, translate_longopt
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 66, in load_module
    module = old(*args, **kwargs)
  File "/tmp/pymonkey/pymonkey.py", line 173, in load_module
    module = importmod(fullname)
  File "/tmp/pymonkey/pymonkey.py", line 94, in importmod
    return __import__(mod, fromlist=[str('__name__')], level=0)
  File "/usr/lib/python3.6/distutils/fancy_getopt.py", line 12, in <module>
    import getopt
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 67, in load_module
    patch_dist(module)
  File "/tmp/pymonkey/.tox/py36/lib/python3.6/site-packages/_virtualenv.py", line 18, in patch_dist
    old_parse_config_files = dist.Distribution.parse_config_files
AttributeError: module 'getopt' has no attribute 'Distribution'

@asottile
Copy link
Contributor

asottile commented Mar 6, 2020

@ofek also lol it's been less than a day chill my dude <3

@gaborbernat
Copy link
Contributor Author

@asottile wait, the patch should only activate for setuptoools/distutils dist, so it does not need to be undone 🤔 do you have a docker build that reproduces your test?

@asottile
Copy link
Contributor

asottile commented Mar 6, 2020

if you clone github.com/asottile/pymonkey and tox -e py3 -- I can make a docker container in ~8 hours when I'm off a plane but that should work on all platforms

@gaborbernat
Copy link
Contributor Author

Ok now tested with that test suite and passes 👍

Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net>
@@ -55,13 +56,24 @@ def find_spec(self, fullname, path, target=None):
try:
spec = find_spec(fullname, path)
if spec is not None:
old = spec.loader.exec_module
# https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
spec.loader = deepcopy(spec.loader) # loaders may be shared, create new that also patches
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this just moves the bug slightly:

  • not all loaders are copyable
  • some loaders depend on being singletons and copying them breaks that invariant

@gaborbernat
Copy link
Contributor Author

gaborbernat commented Mar 6, 2020 via email

@asottile
Copy link
Contributor

asottile commented Mar 6, 2020

not necessarily, it's unlikely someone would go out of their way to block copying if there's no expected interface to reach their loader

mine in particular needs to be a singleton to function but ~kinda hobbles along without it so that's why the tests probably passed


but either way, if it did raise on copying this patch is still broken for it ;)

@gaborbernat
Copy link
Contributor Author

@asottile will fill a follow-up PR to patch only once the loader, not copy, and instead defend the patch itself 🤷‍♂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[20.0.8] import hook assumes existence of exec_module but the api is optional according to PEP 302/451
3 participants