This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: SourceFileLoader does not (fully) accept path-like objects
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.9
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: FFY00, brett.cannon, favonia, gvanrossum, petr.viktorin
Priority: normal Keywords:

Created on 2021-01-06 14:32 by favonia, last changed 2022-04-11 14:59 by admin.

Messages (5)
msg384504 - (view) Author: favonia (favonia) Date: 2021-01-06 14:32
If one uses the loader created by importlib.machinery.SourceFileLoader(module_name, path) in a meta path finder, where path is not a str, then the following error would happen at the moment the module is being imported. Note that, the error would not occur if the corresponding bytecode cache (__pycache__/*.pyc) is present.

  File "/usr/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 972, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 786, in exec_module
  File "<frozen importlib._bootstrap_external>", line 932, in get_code
  File "<frozen importlib._bootstrap_external>", line 604, in _code_to_timestamp_pyc
ValueError: unmarshallable object

Here is an example that could trigger the error. The package unipath is not very important except that unipath.Path is not marshallable. (Any library with a fancy, unmarshallable path-like object would work.) The choice of 'local' and the use of 'os.getcwd()' are also irrelevant. The actual production code is quite complicated, using different path-like objects and convoluted logic. I made up this example from scratch.

import os
import sys
import importlib
import importlib.abc
import importlib.util
from unipath import Path # just an example

class DummyFinder(importlib.abc.MetaPathFinder):
    def __init__(self):
        self.cwd = os.getcwd()
    def find_spec(self, fullname, path, target=None):
        if fullname == 'local':
            initpath = os.path.join(self.cwd, '__init__.py')
            if os.path.isfile(initpath):
                loader = importlib.machinery.SourceFileLoader(fullname, Path(initpath)) # some non-str Path-like object here
                return importlib.util.spec_from_file_location(fullname, initpath,
                        loader = loader, submodule_search_locations=[self.cwd])
        else:
            return None

sys.meta_path.append(DummyFinder())
importlib.import_module("local.test2")

If this is indeed a bug, there might be other classes and functions in importlib that share the same problem.
msg384524 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-01-06 17:37
Maybe Brett can help?
msg384531 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2021-01-06 18:41
importlib is probably not os.PathLike-clean due to its bootstrapping restrictions of not getting to use anything implemented in Python from 'os' (i.e. if it isn't being managed in posixmodule.c then it probably won't work).

If you follow the traceback it's trying to marshal a code object for the eventual .pyc file and failing (https://github.com/python/cpython/blob/faf49573963921033c608b4d2f398309d9f0d2b5/Lib/importlib/_bootstrap_external.py#L604). The real question is why is any unmarshallable object getting passed in the first place since that object is the code object that compile() returned.

Best guess? The compile() function is being given the path-like object (via https://github.com/python/cpython/blob/faf49573963921033c608b4d2f398309d9f0d2b5/Lib/importlib/_bootstrap_external.py#L848) and it's blindly setting it on the code object itself, and then marhsal fails since it can't handle pathlib.Path. If my hunch is right, then the possible solutions are:

- Don't do that 😉
- Make compile() aware of path-like objects
- Have importlib explicitly work around compile()'s shortcoming by doing the right thing for path-like objects before passing in the the 'path' argument.
msg384532 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-01-06 18:46
If Brett's theory is right, compile() has a bug though -- it shouldn't plug a non-string into the code object. Or possibly the code object constructor should reject such objects (or extract the string).
msg401280 - (view) Author: Petr Viktorin (petr.viktorin) * (Python committer) Date: 2021-09-07 14:10
I just filed the slightly more general bpo-45127.
History
Date User Action Args
2022-04-11 14:59:39adminsetgithub: 87005
2021-09-07 14:10:09petr.viktorinsetnosy: + petr.viktorin
messages: + msg401280
2021-07-31 22:27:11FFY00setnosy: + FFY00
2021-01-06 18:46:31gvanrossumsetmessages: + msg384532
2021-01-06 18:41:54brett.cannonsetmessages: + msg384531
2021-01-06 17:37:45gvanrossumsetnosy: + gvanrossum, brett.cannon
messages: + msg384524
2021-01-06 14:32:12favoniacreate